Fwd: HINT: more control and pkg management using package users

Gerard Beekmans gerard at linuxfromscratch.org
Tue Sep 4 07:34:38 PDT 2001


----- Forwarded message from Matthias Benkmann <matthias at winterdrache.de> -----

From: "Matthias Benkmann" <matthias at winterdrache.de>
To: lfs-discuss at linuxfromscratch.org
Subject: HINT: more control and pkg management using package users
Date: Tue, 4 Sep 2001 00:31:48 +0200
Delivered-To: gerard at localhost.gwaihir.linuxfromscratch.org
Delivered-To: gerard at linuxfromscratch.org
Delivered-To: lfs-discuss at linuxfromscratch.org
Priority: normal
X-mailer: Pegasus Mail for Win32 (v3.12c)
X-listar-version: Listar v0.129a
X-original-sender: matthias at winterdrache.de
Precedence: bulk
Reply-To: lfs-discuss at linuxfromscratch.org
X-list: lfs-discuss
X-UIDL: :FH"!dG+!!iA\!!V:0"!

Content-Description: Mail message body
Well, I have taken the time to write an all new hint on how to use package 
users to give you more control over your system and a basic package 
management system. It's attached to this message.

MSB

----
Nietzsche: God is dead!

God: Nietzsche is dead!



Content-Description: Text from file 'more_control_and_pkg_management.txt'
TITLE:		More control and package management using package users (v0.1)
LFS VERSION:	All
AUTHOR:		Matthias S. Benkmann <m.s.b at gmx.net>

SYNOPSIS:
	You want to know which packages your files belong to ?
	You want to deinstall software that doesn't have make uninstall ?
	You are bothered by programs installed setuid root behind your back ?
	You don't want packages to overwrite files from other packages ?
	You don't like package managers like RPM ?
	YOU WANT TOTAL CONTROL USING ONLY UNIX BUILTINS ?

HINT:

LEGALESE: The author does not take responsibility for any damage of any kind
caused directly or indirectly by applying the methods described below. 
There is no warranty of any kind, not even the implied warranty of fitness for
the purposes mentioned in the text, on any of the programs/scripts/commands 
listed below and the author may not be held liable for any damage of any kind 
caused by the execution of said programs/scripts/commands.
Proceed at your own risk!

########################################################################### 
 Introduction
###########################################################################

Let's say I have written a program that you would like to use. To make it
easier for you I come over to install it for you. Would you give me the root
account and then leave the room ? No ? Then why do you give it to complete
strangers who you have never seen in your life to install software packages
pulled from some internet server whose contents that are going to be spread 
all over your system are not even listed in the README and come with no 
warranty ?
It is a mystery why Unix admins who wouldn't even trust their employer with
more than a normal user account carelessly execute complex and incomprehensible
installation scripts with full root rights.

Users and groups are the basic security principle in a Unix system. They have
been used successfully for a long time to control who has created a file and 
who is allowed to delete or change this file. But this control has only been
imposed on the files of ordinary users. What a waste! I suggest to extend
this control to all system files.


#############################################################################
 Package users
#############################################################################

The basic idea of this scheme is easily explained. Every package belongs to a 
certain "package user". This is a special user account whose HOME directory
is /usr/src/<package> (or wherever you keep the tarballs for the package).
Usually this account has no password so that only root can su to a package 
user, which ensures that package users do not open an additional way into the 
system and undermine security.
Of course you *may* set passwords anyway to allow a co-admin who you want to
be able to install and maintain certain software packages to do so without 
having access to the actual root account. This co-admin could for instance
install, delete, change additional libraries which might be necessary for his
workgroup. He would be unable, though, to remove or modify libraries which 
don't belong to him/her, such as libc.

Every package user has a primary group that reflects the purpose of the
package, such as "system" for essential packages, "devel" for packages
related to software development and so on. There are other useful
organizational schemes, of course. You might want to have the group reflect
the workgroup of the above-mentioned co-admin who installed the file for 
instance.

In addition to the primary group, there is the secondary group which is the
same for all package users. This is the "install" group. Every directory
that packages are allowed to install stuff in belongs to the install group.
This includes directories such as /bin and /usr/bin but excludes directories
like /root or /. The directories owned by the install group are always
group writeable. Without further preparation this does not give added security
or control because every package could replace the files from a different
package (the change would be visible in the output from ls -la, though).
For this reason we make all install directories sticky. The sticky attribute
allows users to create new files and delete or modify their own files in
the directory while preventing them from writing or deleting files from other
users.


############################################################################
 How to install software
############################################################################

Isn't this all very tedious ? Not if you use this script:

----------------------- begin /sbin/install_package ------------------------
#!/bin/sh
if [ $# -ne 3 ]; then
echo USAGE: install_package description name group
echo "!!Don't forget to put description in quotes if it contains spaces.!!"
echo "This will create a new package user name in group. This will set up"
echo "a home directory for this package user. After that, this script will"
echo "automatically su to the new user so that you can begin with the"
echo "installation right away. "
exit 1
fi

if [ $UID -ne 0 ]; then echo Please run this script as root. ; exit 1; fi
add_package_user "${1}" $2 10000 20000 $3 10000 20000 || exit 1
su $2
----------------------- end /sbin/install_package ----------------------------

The above script is a shortcut for the following script, which does the
actual adding of the package user in a certain range of UIDs and GIDs:

------------------------ begin /sbin/add_package_user -----------------------
#!/bin/sh

if [ $# -ne 7 ]; then
echo 'Copyright (c) 2000 Matthias Benkmann'
echo \
'USAGE: add_package_user description name minuid maxuid group mingid maxgid'
echo 
echo Don\'t forget to put description in quotes. 
echo description must be valid for a /etc/passwd entry, that means especially
echo that it must not contain \":\".
echo If group doesn\'t exist, it will be added. 
echo group will be the user\'s main group. 
echo 'The user will also belong to group "install" (created if necessary).'
echo 'A home directory /usr/src/name will be created if it doesn'"'"'t exist'
echo 'and the files from /etc/skel-package will be copied into it (existing'
echo 'target files will *not* be overwritten)'
echo 'minuid (incl.) and maxuid (excl.)  determine'
echo 'the range of UIDs used by this script. The script will pick the first'
echo 'UID greater than all UIDs from this range already in use.' 
echo 'If that UID is out of range (i.e. equal to maxuid), the'
echo 'script will pick the first available UID in this range. If the range is'
echo \
'full, the script will give up. This process ensures that UIDs for packages'
echo 'that have been removed, don'\''t get reassigned unless really necessary.'
echo \
'This avoids trouble if some files were overlooked when removing the package.'
echo 'Otherwise these files might be assigned to the wrong package.'
echo 'mingid (incl.) and maxgid (excl.) define'
echo 'the permissible range for the GID of group if it has to be added (and '
echo 'the install group if it must be added).'
exit 1
fi

grpfile=/etc/group
passwd=/etc/passwd
homebase=/usr/src
skel=/etc/skel-package
description=$1
name=$2
minuid=$3
maxuid=$4
gname=$5
mingid=$6
maxgid=$7
home=$homebase/$name

if [ $UID -ne 0 ]; then echo Please run this script as root. ; exit 1; fi

#test if user already exists
grep "^$name:.*" $passwd
if [ $? -eq 0 ]; then 
  echo 'Package user does already exist! Do su '$name' to do maintenance work.'
  exit 1
fi 

#test if minuid, maxuid, mingid and maxgid are integers, otherwise set
#to defaults.
error=0
expr ${minuid} + 1 2>/dev/null 1>&2 || error=1
expr ${maxuid} + 1 2>/dev/null 1>&2 || error=1
expr ${mingid} + 1 2>/dev/null 1>&2 || error=1
expr ${maxgid} + 1 2>/dev/null 1>&2 || error=1

if [ $error -eq 1 ]; then
  echo Error: Illegal numeric value!
  exit 1
fi

if [ $minuid -ge $maxuid ]; then
  echo 'Error: minuid must be less than maxuid !' 
  exit 1
fi

if [ $mingid -ge $maxgid ]; then
  echo 'Error: mingid must be less than maxgid !' 
  exit 1
fi


uidlist=`cut -d : -f 3 $passwd | sort -n`

#find last used UID within range
u=0
for i in $uidlist
do
  if [ $i -ge $maxuid ]; then break; fi
  if [ $i -ge $minuid ]; then u=$i; fi 
done

#if no UID from the range is used, pick the first, otherwise pick the one
#immediately following the last UID in use.
if [ $u -eq 0 ]; then u=$minuid; else u=`expr $u + 1`; fi

#if the last UID used from the range is maxuid-1, i.e. we may 
#not use its successor as UID, then we look for the first unused uid
#in the range.
if [ $u -ge $maxuid ]; then
  u=$minuid
  for i in $uidlist
  do
    if [ $u -eq $i ]; then u=`expr $u + 1` ; fi
    if [ $i -ge $maxuid ]; then break; fi
  done  

  if [ $u -ge $maxuid ]; then
    echo Error: UID range is full!
    exit 1
  fi
fi

echo Will create user $name with uid: $u

unset uidlist

#               group stuff

#execute the following for gname and "install" to get gids for those 2 groups

g=0
creategroup=0
for group in install $gname
do
  oldg=$g #save gid from previous run
  createinstall=$creategroup
  creategroup=0
 
  #test if group already exists and extract gid if so
  g=`grep ^${group}:.\* /etc/group | cut -d : -f 3 -`

  #if group does not exist, then check range for a free gid
  if [ z$g = z ]; then 
    creategroup=1
    
    gidlist=`cut -d : -f 3 $grpfile | sort -n`

    #find last used GID within range
    g=0
    for i in $gidlist
    do
      if [ $i -ge $maxgid ]; then break; fi
      if [ $i -ge $mingid ]; then g=$i; fi
    done

    #if no GID from the range is used, pick the first, otherwise pick the one
    #immediately following the last GID in use.
    if [ $g -eq 0 ]; then g=$mingid; else g=`expr $g + 1`; fi
    
    #don't reuse gid from previous run 
    if [ $g -eq $oldg ]; then g=`expr $g + 1`; fi

    #if the last GID used from the range is maxgid-1, i.e. we may 
    #not use its successor as GID, then we look for the first unused gid
    #in the range.
    if [ $g -ge $maxgid ]; then
      g=$mingid
      for i in $gidlist
      do
        if [ $g -eq $i ]; then g=`expr $g + 1` ; fi
        if [ $g -eq $oldg ]; then g=`expr $g + 1` ; fi
        if [ $i -ge $maxgid ]; then break; fi
      done  

      if [ $g -ge $maxgid ]; then
        echo Error: GID range is full!
        exit 1
      fi
    fi
  fi
done

unset gidlist

if [ $createinstall -eq 1 ]; then
  echo Creating group install with gid $oldg
  groupadd -g $oldg install || exit 1
else
  echo Group install has gid $oldg
fi
if [ $creategroup -eq 1 ]; then
  echo Creating group $gname with gid $g
  groupadd -g $g $gname || exit 1
else 
  echo Group $gname has gid $g
fi



useradd -c "${description}" -d ${home} -g ${gname} -G install \
        -s /bin/bash -u ${u} ${name}  || exit 1

if [ ! -d $home ]; then mkdir $home || exit 1; fi

yes n|cp -ai -R ${skel}/{[^.],.[^.],..?}* ${home} 2>/dev/null >/dev/null

cd ${home}
chown --recursive ${u}.${g} .


exit 0
------------------------ end /sbin/add_package_user ------------------------


If you use these scrips, you can install a new package like this

install_package foo 'Foo description' foogroup

and the package user will be created and you will automatically be su'd to it
and put into the package users home directory. Now just copy the tarball
from your download directory, untar and install. It's no more work than
doing it as root.


############################################################################
 Pitfalls and Solutions
############################################################################
 
There are some pitfalls when you use this system. The following situations are
common:

1. A package install script is poorly written and insists on changing
   ownership/permissions of system directories or its own files.
   
2. A package install script has a legitimate reason to change 
   ownership/permissions of files it installs.
   
3. Package A contains a program that is also part of package B. Package B
   was installed first.
   
4. A package tests if the user installing it is root and does not install
   certain programs otherwise.
   
5. A package creates a directory that other packages need to write to.

1-3 usually result in an "Operation not permitted" error during make install.
Note that this is not actually a flaw of the package user scheme, it's a
feature. It's the reason why we made the install directories sticky and use
package users in the first place. We don't want stuff like this to happen
without our (divine :-) intervention. Fixing these issues is usually easy:

1. Look at the output from "make install". It will contain the offending
   command such as "install --owner root foo /bin" right before the
   "Operation not permitted" error. grep through the Makefiles of the package
   and remove the offending switch (in this case --owner). Sometimes you may
   not want to remove it altogether but may need to change it to something
   harmless. For instance "install -m 4755 ..." which tries to set the
   setuid bit should be changed to "install -m 755 ...".
   You can automate these changes with a sed script or use a wrapper script
   around install (see below) to avoid having to manually change Makefiles.
   Note that you either have to perform these changes *after* the configure
   step or you have to modify the source Makefiles (usually Makefile.in or
   Makefile.am).

2. This is basically like 1 but in this case the package actually has a reason
   (that you accept) for doing what it does. This is seldom but happens for
   some packages that install setuid root programs that you want to have
   setuid root, such as ping. In this case, act like in case 1 and execute
   the appropriate changes manually as root afterwards,
   such as chown root.root /bin/ping && chmod 4755 /bin/ping.
   
3. In this case you have to decide which package you actually want to provide
   the program in question. If you want to replace package B's program with
   that of package A, simply delete the program manually as root.
   If you don't want package A to overwrite the program with its own,
   grep through the Makefiles for the name of the program in question (and
   its manpages). Usually you will find a list like
   PROGRAMS=foo bar fubar barfu
   and it is sufficient to remove the program from this list. In very rare
   cases you may need to remove some "install <programname> <destination>"
   lines from the Makefile.
   
4. This case is usually documented in the INSTALL file of the package and a 
   make target to install the programs left out, such as "install-root", is 
   provided. So this is not normally an issue.

5. This case is easy to fix by assigning the directory in question to the 
   install group and making it group-writeable and sticky. This does not even
   have to be done as root. The package user who created the directory has the
   permissions to do it. So you can insert an appropriate line into the 
   Makefile.

Manually changing Makefiles is annoying, even if it happens only occasionally.
Sed scripts do help but they have to be custom fitted to every package that
needs them. There is a better solution, though. While building LFS with the
package user scheme, I have noticed that although there are a lot of possible
things a Makefile could contain which would trigger an 
"Operation not permitted" error, only few of them are actually used.
So far (I still have a few packages to test as of this writing) I have only
encountered these problems in connection with mkdir and install. I have
written the following scripts to handle them automatically. If you make
sure the directory where these scripts are located (in my case /usr/src/lfs)
is the first in the PATH for every package user, you should be able to install
most LFS packages without having to fix any Makefiles manually. The script
outputs the original command line that triggered an override, preceded by
"***", so you can easily check the "make install" output and see if there are
any commands you might want to execute manually as root (such as making ping
setuid root). Here are the scripts:

------------------------- begin /usr/src/lfs/mkdir --------------------------
#!/bin/sh

#This script has the main purpose of assigning all directories created
#in /usr/share/locale to the install group so that other programs can
#put their locale files into them, too.

watchdir=/usr/share/locale

cmdline="$@"

dirs=""
for((i=$#; $i; i=i-1))
do
  a="$1"
  shift 1
  case "$a" in
    $watchdir/*) dirs="$dirs ""`expr $a : "$watchdir/\(.*\)"`" 
                 set -- "$@" "$a" 
                 ;;
    *) set -- "$@" "$a" ;;
  esac
done

PATH=/bin:/usr/bin:$PATH
mkdir "$@" || exit $?

test z"$dirs" != z &&
echo 1>&2 '***' mkdir "$cmdline"
for dir in $dirs ; do
  cumuldir=""
  for d in `echo $dirs | sed 's#/# #g' -` ; do
    cumuldir=$cumuldir$d/
    chgrp install $watchdir/$cumuldir 2>/dev/null
    chmod g+w,o+t $watchdir/$cumuldir 2>/dev/null
  done  
done
exit 0
------------------------- end /usr/src/lfs/mkdir --------------------------

------------------------- begin /usr/src/lfs/install ----------------------
#!/bin/sh

localedir=/usr/share/locale
cmdline="$@"
manpagesowner=manpages

	#********** test if we create directories ********************
if [ \( z"$1" = z"-d" \) -o \( z"$1" = z"-m" -a z"$3" = z"-d" \) ]; then  
  locdirs=""
  notify=0
  havedir=0
  for((i=$#; $i; i=i-1))
  do
    a="$1"
    shift 1
    case "$a" in
      -o|-g|--owner|--group) notify=1 
      			shift 1
          		set -- "$@" 
          ;;
      $localedir/*) if [ ! -d "$a" ]; then
      		      locdirs="$locdirs ""`expr $a : "$localedir/\(.*\)"`" 
                      set -- "$@" "$a"
                      havedir=1
                    else
                      notify=1
                      set -- "$@"
                    fi  
                   ;;
      */*|/sbin) if [ ! -d "$a" ]; then 
      	     set -- "$@" "$a" 
      	     havedir=1
      	   else
      	     notify=1
      	     set -- "$@"
      	   fi             
      	   ;;
      *) set -- "$@" "$a" ;;
    esac
  done
  
  test $notify -eq 1 -o z"$locdirs" != z && \
  					echo 1>&2 '***' install "$cmdline"

  test $havedir -eq 0 && exit 0

  PATH=/bin:/usr/bin:$PATH
  install "$@" || exit $?
  
  test z"$locdirs" != z &&
  for dir in $locdirs ; do
    cumuldir=""
    for d in `echo $locdirs | sed 's#/# #g' -` ; do
      cumuldir=$cumuldir$d/
      if [ -d $localedir/$cumuldir ]; then
        chgrp install $localedir/$cumuldir 2>/dev/null
        chmod g+w,o+t $localedir/$cumuldir 2>/dev/null
      fi  
    done  
  done

else  #if "$1" != "-d"  ,i.e. we do not create directories *****************
  notify=0
  for((i=$#; $i; i=i-1))
  do
    a="$1"
    shift 1
    case "$a" in
     -m)      set -- "$@" "$a" 
              a="$1"
              shift 1
              case "$a" in
                4755) notify=1 ; set -- "$@" "755" ;;
                *) set -- "$@" "$a"  ;;
              esac
          ;;
      -m4755) notify=1 ; set -- "$@" "-m755" ;;
      -o|-g|--owner|--group)    notify=1 
      		shift 1
          	set -- "$@" 
          ;;
      */man/man?/*) 
                if [ -e "$a" -a ! -O "$a" ]; then
                  if [ `find "$a" -printf \%u` = $manpagesowner ]; then
                    notify=1
                    set -- "$@" not_installed
                  else
                    set -- "$@" "$a"
                  fi  
                else
                  set -- "$@" "$a"
                fi
          ;;    
      *) set -- "$@" "$a" ;;
    esac
  done

  test $notify -eq 1 && echo 1>&2 '***' install "$cmdline"

  PATH=/bin:/usr/bin:$PATH
  install "$@" || exit $?
fi

exit 0
------------------------- end /usr/src/lfs/install --------------------------

If you improve any of these scripts, I would be grateful if you could mail
me your changes together with a note saying which package prompted the
change. Note that these scripts are supposed to have minimal intrusiveness.
So writing a common case "-m*)" instead of the case "-m4755" is undesirable.
The scripts should only deal with the problems that have actually been
encountered, not all possible problems.

#############################################################################
 LFS specifics
#############################################################################
 
For the user name of the package users I always use the name of the package
without the version number, including dashes and possibly exceeding 8
characters in length, e.g. "util-linux". 
Aside from ls -l which chops off the last characters
of the user name I have not noticed any problems with this. The other programs
I have tested seem to deal with these user names just fine. 
If you encounter problems, please report them to me.

Now how do we apply the above to the building of an LFS system ?

##########################################################################
Pre-chroot phase 
##########################################################################

There are no special precautions to take for the pre-chroot
phase (chapter 5). We don't use package users here. This does not mean we
install as root. I suggest building the pre-chroot system as a normal user.
 
##########################################################################  
 Chroot phase - preparing the LFS system: 
##########################################################################
 
This is the tricky part. Once we
have entered chroot, we have to assign install directories to the
install group and make them group writeable. However, since glibc is not
yet installed, we can't use user names, yet. Furthermore, groupadd and useradd
which are part of the shadow package are not installed, yet.
This means that we can't create any package users, at least not without
editing /etc/group and /etc/passwd manually. 
But don't despair. The following
scripts make nice replacements for useradd and groupadd as long as shadow
isn't there. Note that they only accept the exact syntax with which the
useradd and groupadd commands are used in the add_package_user script.
I assume you will be using the install_package/add_package_user scripts to
add package users to the system. Here are the scripts:

----------------------------- begin /sbin/useradd ----------------------------  
#!/bin/sh
if [ $# -ne 13 -o z$1 != z-c -o z$3 != z-d \
 -o z$5 != z-g -o z$7 != z-G -o z$9 != z-s -o z${11} != z-u ]; then
echo USAGE: useradd -c description -d home \
 -g maingroup -G addgroup -s shell -u uid login 
exit 1
fi

#test if user already exists
grep "^${13}:.*" /etc/passwd 
if [ $? -eq 0 ]; then
  echo Error: User does already exist
  exit 1
fi       

g=`grep ^${6}:.\* /etc/group | cut -d : -f 3 -`
if [ z${g} = z ]; then
  echo Error: Group ${6} does not exist!
  exit 1
fi

grep ^${8}:.\* /etc/group >/dev/null || \
{
  echo Error: Group ${8} does not exist!
  exit 1
}

cp /etc/passwd /tmp/passwd123456
echo "${13}:x:${12}:$g:$2:$4:/bin/bash" \
| sort -t : -k3,3n -m /tmp/passwd123456 - > /etc/passwd


cp /etc/group /tmp/group123456
sed  -e 's/^\('"${8}"':[^:]*:[0-9]*:..*\)$/\1,'"${13}"'/' \
     -e 's/^\('"${8}"':[^:]*:[0-9]*\):$/\1:'"${13}"'/' \
     						/tmp/group123456 >/etc/group
 
----------------------------- end /sbin/useradd ----------------------------  

----------------------------- begin /sbin/groupadd --------------------------
#!/bin/sh
if [ $# -ne 3 -o z$1 != z-g ]; then
echo USAGE: groupadd -g gid groupname
exit 1
fi

#test if group already exists
grep "^${3}:.*" /etc/group 
if [ $? -eq 0 ]; then
  echo Error: Group does already exist
  exit 1
fi       

cp /etc/group /tmp/group123456
echo ${3}:x:${2}: | sort -t : -k3,3n -m /tmp/group123456 - > /etc/group

----------------------------- end /sbin/groupadd ----------------------------

These scripts overcome the problem of the missing shadow utilities. There is
still the problem that other programs such as chown can not resolve usernames
before glibc is installed. Fortunately most programs accept a numeric UID
or GID. There is one nasty exception, though: su. The most fundamental
program when using package users doesn't swallow numeric ids :-|

Now how do we become a package user ? 
If you guessed that I have a written a script to replace it, you guessed ...
... wrong :-) I have written a C program to replace it. 

It just happens that sh-utils
contains an su program which is not used in LFS. It gets installed 
statically linked during the pre-chroot phase, is never used there (assuming
a standard LFS build), gets replaced with a dynamically linked version when 
sh-utils is reinstalled and then is finally replaced by the su from shadow 
which is the version we will use when the LFS system is finished. So all
you need to do is to replace the file src/su.c in the sh-utils source tree
with the following file before compiling your chapter 5 sh-utils:

------------ begin /usr/src/sh-utils/sh-utils-<version>/src/su.c ------------
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <grp.h>

#define NUMGIDS 1024

int main(int argc,char* argv[])
{
  char* Res;
  struct passwd* pw;
  char Buffy[4096];
  uid_t tuid=-1,uid;
  gid_t gid;
  int i;
  FILE* File;
  char* command=NULL;
  char* shell;
  char* HOME;
  gid_t gid_list[NUMGIDS];
  ssize_t NumGids=0;

  if ((argc==4) && (strcmp(argv[2],"-c")==0)) command=argv[3]; else
  if (argc!=2)
  {
    fprintf(stdout,"USAGE: su username|uid [-c command]\n");
    return 1;
  };

  i=0;
  while(isdigit(argv[1][i])) ++i;
  if (argv[1][i]==0) tuid=atol(argv[1]);

  File=fopen("/etc/passwd","rb");
  if (File==NULL) {perror("/etc/passwd"); return 1;};
  
  while(1)
  {
    Res=fgets(Buffy,1024,File);
    if (Res==NULL) {
      if (errno!=0) perror("/etc/passwd"); 
        else fprintf(stderr,"User not found!\n");
      return 1;
    };
    
    Res=strtok(Buffy,":");
    if (Res==NULL) continue;
    strtok(NULL,":");
    uid=atol(strtok(NULL,":"));
    gid=atol(strtok(NULL,":"));
    strtok(NULL,":");
    HOME=strtok(NULL,":");
    shell=strtok(NULL,":");
    if ((tuid==uid) || (strcmp(argv[1],Buffy)==0)) break;
  };
  HOME=strdup(HOME);
  shell=strdup(shell);
  
  File=fopen("/etc/group","rb");
  if (File==NULL) {perror("/etc/group"); return 1;};
  
  while(1)
  {
ContinueReadingEtcGroup:
    Res=fgets(Buffy,1024,File);
    if (Res==NULL) {
      if (errno!=0) {perror("/etc/group"); return 1;} else break;
    };
    
    Res=strtok(Buffy,":,\n");
    if (Res==NULL) continue;
    strtok(NULL,":,\n");
    gid_list[NumGids]=atol(strtok(NULL,":,\n"));
    Res=strtok(NULL,":,\n");

    while(Res!=NULL)
    {
      if (strcmp(Res,argv[1])==0) 
      {
        ++NumGids; 
        if (NumGids>=NUMGIDS) goto SetNewIdentity; 
        		 else goto ContinueReadingEtcGroup;
      };
      Res=strtok(NULL,":,\n");
    };
  };

SetNewIdentity:
  if (command==NULL) command=shell;
  setenv("HOME",HOME,1);
  setgroups(NumGids,gid_list);
  setgid(gid);
  setuid(uid);
  errno=0;
  i=system(command);
  if (((i<0) || (i==127)) && (errno!=0)) {perror("/bin/sh"); return 1;};
  return i;
};
------------ end /usr/src/sh-utils/sh-utils-<version>/src/su.c ------------

This su program accepts user names as well as numeric UIDs. It does its own
name resolution using /etc/passwd so it works even without glibc being
installed.

With the above scripts and su program we now have everything we need to
begin our chroot phase. Note that I have not actually tested the following
commands (I use Makefiles that do this stuff). Don't blindly cut and paste.
Use your brain first.

After you have chroot'ed do

install_package glibc "GNU C library" system

which will create the install group, the system group and a user glibc.
This also su's to the new user but we don't want to install glibc just yet,
so do

exit

to become root again. Now use 

chown 0.10000 `cat /tmp/installdirs`
chmod ug=rwx,o=rx `cat /tmp/installdirs`

to assign directories to the install group. Note that we do not make the
directories sticky, yet. This is because they still contain files belonging
to root or some unknown user (if you installed pre-chroot as non-root). These
files must be overwriteable.
The above commands assume that you have not
changed my scripts in which case the install group will have GID 10000. The
file /tmp/installdirs is the following list:

---------------------- begin /tmp/installdirs ------------------------------
/usr/bin
/usr/etc
/usr/sbin
/usr/include
/usr/lib
/usr/man/man?
/usr/doc
/usr/info
/usr/local/man/man?
/usr/local/doc
/usr/share
/usr/share/dict
/usr/share/doc
/usr/share/info
/usr/share/locale
/usr/share/man/man1
/usr/share/man/man2
/usr/share/man/man3
/usr/share/man/man4
/usr/share/man/man5
/usr/share/man/man6
/usr/share/man/man7
/usr/share/man/man8
/usr/share/nls
/usr/share/misc
/usr/share/terminfo
/usr/share/zoneinfo
/usr/share/i18n
/usr/share/aclocal
/usr/local/bin
/usr/local/etc
/usr/local/include
/usr/local/lib
/usr/local/sbin
/usr/local/share
/usr/local/share/dict
/usr/local/share/doc
/usr/local/share/info
/usr/local/share/locale
/usr/local/share/man/man1
/usr/local/share/man/man2
/usr/local/share/man/man3
/usr/local/share/man/man4
/usr/local/share/man/man5
/usr/local/share/man/man6
/usr/local/share/man/man7
/usr/local/share/man/man8
/usr/local/share/nls
/usr/local/share/misc
/usr/local/share/terminfo
/usr/local/share/zoneinfo
/etc
/sbin
/bin
/lib
---------------------- end /tmp/installdirs --------------------------------

Now do

chown 0.10000 /usr/share/info/dir
chmod ug=rw,o=r /usr/share/info/dir

which makes sure packages can install their info pages. 

#########################################################################
 Chroot phase -installing the packages 
#########################################################################

Now we can finally install glibc. Do

su glibc

to become the glibc package user. Note that the mkdir and install wrapper
don't work properly during the installation of glibc because they use
the name "install" rather than the gid. This means we have to do the
following *after* installing glibc:

find /usr/share/locale/* -type d -user glibc -exec chmod ug=rwx,o=rxt \{\} \;\
					 -exec chgrp install \{\} \;


Now do

exit

to become root again and begin installing the next package (whatever that
is in the book you are reading) with

install_package <packagename> <description> <group>

The following is a list of the LFS packages with the user.group names I have
assigned (you may choose your own of course) and any special notes I made
when I installed them. If you use the mkdir and install wrapper, you should
not have any trouble.

glibc.system:
  see above.

MAKEDEV: not installed as a package user

man-pages.docs
findutils.util
mawk.devel
ncurses.system
gcc.devel
bison.devel
less.util
groff.util
man.util
perl.devel (maybe perl.util is better)
m4.devel
texinfo.util
autoconf.devel
automake.devel:
	Creates the directory /usr/share/aclocal. This directory is written to
	by other packages, so chgrp install and chmod g+w,o+t it.

bash.system
flex.devel
file.util
libtool.devel
bin86.devel
binutils.devel
bzip2.util
gettext.system
kbd.system
diffutils.util
e2fsprogs.util
fileutils.util (maybe fileutils.system is better)
grep.util
gzip.util
make.devel

linux kernel: not installed as package user
modutils.system:
	The modutils check if modules are owned by root before using them.
	This is the reason why the linux kernel should be compiled and
	installed as root, rather than as a package user.

netkit-base.net
patch.util
procinfo.util
procps.util
psmisc.util
sed.util
sh-utils.util (maybe sh-utils.system is better)
net-tools.net
shadow.system:
	This package installs quite a few setuid root progs. If you're not
	religious about package users you might want to install this one
	as root.root to make life easier for yourself.

sysklogd.system
tar.util
textutils.util
util-linux.system


After installing everything, make the install directories sticky:

chmod ug=rwx,o=rxt `cat /tmp/installdirs`

Finally you should
use find to find files that still belong to the user you
installed pre-chroot with. You can also use find to identify any directories
that are group writeable and not sticky. You should also check for setuid
and setgid programs that don't belong to root. Apply your common sense to
deal with these files (or send me a mail so I can mention the issue here)

###########################################################################
 Tips
###########################################################################
 
You may place symlinks in /etc/skel-package. These are copied as symlinks.
A useful symlink to place in /etc/skel-package is one like

.bashrc -> /etc/bashrc_package 

with something like the following:

---------------------- begin /etc/bashrc_basic ----------------------------
#This file should be sourced by all users' .bashrc files

if [ $UID -eq 0 ]; then
  export PATH=/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin
else
  export PATH=/usr/local/bin:/bin:/usr/bin
fi

  #set a red prompt when user is root. If a user wants to override this
  #s/he should use USER_PROMPT_COMMAND instead of PROMPT_COMMAND in order to 
  #keep 
  #root's prompt when su'ed to root. If a user wants to change PS1 directly
  #s/he should do "unset USER_PROMPT_COMMAND".
export PROMPT_COMMAND='if [ $UID -eq 0 ]; then \
  PS1="\[\033[0;31m\]root@\h:"`pwd -P`"# \[\033[0m\]" ; \
    else eval $USER_PROMPT_COMMAND ; fi'
  #make PROMPT_COMMAND read-only to protect against careless users
declare -r PROMPT_COMMAND
  #set a reasonable default USER_PROMPT_COMMAND
export USER_PROMPT_COMMAND='PS1="\u@\h:"`pwd`"> "'

  #make keys (del, bs, home, end,...) work normal
export INPUTRC=/etc/inputrc
bind -f $INPUTRC
---------------------- end /etc/bashrc_basic ------------------------------

---------------------- begin /etc/bashrc_package --------------------------
#make prompt reflect that we are a package user. Do it via USER_PROMPT_COMMAND
#rather than PROMPT_COMMAND because we want to keep root's prompt if we su 
#to root
export USER_PROMPT_COMMAND='PS1="package \u:"`pwd`"> "'

#The following command will put us in the home directory. 
cd
---------------------- end /etc/bashrc_package ----------------------------

Another useful file to place in /etc/skel-package is a .project file like
this:

---------------------- begin /etc/skel-package/.project ------------------
DESCRIPTION:
  bogus package
CONTENTS:
  foo,bar,fubar
LAST UPDATED:
  30 Feb 2042
DOWNLOAD LOCATION:
  ftp://ftp.gnu.org/gnu/foo/
WEB SITE:
  <none>
INSTALL NOTES:
GENERAL NOTES:
---------------------- end /etc/skel-package/.project ------------------

Update this file whenever you re/install a package. It is called .project
so that it is automatically displayed when you issue the command 
"finger <package>" or "pinky -l <package>".



----- End forwarded message -----

-- 
Gerard Beekmans
www.linuxfromscratch.org

-*- If Linux doesn't have the solution, you have the wrong problem -*-



More information about the hints mailing list