#!/usr/bin/perl -wT
my $ID = q$Id: localuserinfo,v 1.7 2007-02-07 15:08:09 jonas Exp $;
#
# localuserinfo -- List fullname for each user
#
# Written by Jonas Smedegaard <dr@jones.dk>
# Copyright 2003-2007 Jonas Smedegaard <dr@jones.dk>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# TODO: Options to suppress infochunks
#

use strict;

use Getopt::Long;
use User::pwent;
use User::grent;

our $verbose = 1;		# should we be verbose?
my $ignore_badname = 0;		# should we ignore bad names?
my $include_username = 1;
my $include_fullname = 1;
my $include_office = 0;
my $include_officehints = 1;
my $include_workphone = 1;
my $include_homephone = 1;
my $include_other = 0;
my $include_addresshints = 1;
my $include_mail = 0;
my $include_groupname = 0;
my $include_members = 0;
my $include_groups = 0;
my $include_grouphints = 1;
our $custom_template;

our @names;

# Resolve version number from CVS id
my $version = join (' ', (split (' ', $ID))[1..3]);
$version =~ s/,v\b//;
$version =~ s/(\S+)$/($1)/;

our $maildomain_path = '/etc/mailname';
our $maildomain = &get_maildomain;

# Parse options, sanity checks
unless (
	GetOptions (
		"quiet|q" => sub { $verbose = 0 },
		"username" => \$include_username,
		"fullname|n" => \$include_fullname,
		"office" => \$include_office,
		"officehints" => \$include_officehints,
		"workphone" => \$include_workphone,
		"homephone" => \$include_homephone,
		"other" => \$include_other,
		"addresshints" => \$include_addresshints,
		"mail|m" => \$include_mail,
		"groupname" => \$include_groupname,
		"members" => \$include_members,
		"groups" => \$include_groups,
		"grouphints" => \$include_grouphints,
		"custom=s" => \$custom_template,
		"ignore-badname" => \$ignore_badname,
		"help|h" => sub { &usage(); exit 0 },
		"version|v" => sub { &version(); exit 0 },
		"debug" => sub { $verbose = 2 }
	)
) {
	&usage();
	exit 1;
}

while (defined(my $arg = shift(@ARGV))) {
	push (@names, $arg);
}

# TODO: Support custom ordering and custom delimiter
my @infochunks;
push (@infochunks, 'username') if ($include_username);
push (@infochunks, 'fullname') if ($include_fullname);
push (@infochunks, 'office') if ($include_office);
push (@infochunks, 'officehints') if ($include_officehints);
push (@infochunks, 'workphone') if ($include_workphone);
push (@infochunks, 'homephone') if ($include_homephone);
push (@infochunks, 'other') if ($include_other);
push (@infochunks, 'addresshints') if ($include_addresshints);
push (@infochunks, 'mailaddress') if ($include_mail);
push (@infochunks, 'groupname') if ($include_groupname);
push (@infochunks, 'members') if ($include_members);
push (@infochunks, 'groups') if ($include_groups);
push (@infochunks, 'grouphints') if ($include_grouphints);
my $template = $custom_template ? $custom_template : '%' . join('% %', @infochunks) . '%';

my %groups;
while (my $gr = getgrent()) {

	foreach my $member (@{$gr->members}) {
		push @{$groups{$member}}, $gr->name unless ($member eq $gr->name);
	}
}

# TODO: Rewrite to batch-resolve userinfo for all users before using any
while (my $username = shift @names) {

	my $string;

	my ($fullname, $office, $workphone, $homephone, $other, $groupname, $members, $groups, $officehints, $addresshints, $grouphints) = &getuserinfo($username);
	my $mailaddress = "$username\@$maildomain";

	$string = $template;
	$string =~ s/\%username\%/$username/g;
	$string =~ s/\%fullname\%/$fullname/g;
	$string =~ s/\%office\%/$office/g;
	$string =~ s/\%workphone\%/$workphone/g;
	$string =~ s/\%homephone\%/$homephone/g;
	$string =~ s/\%other\%/$other/g;
	$string =~ s/\%officehints\%/@$officehints/g;
	$string =~ s/\%addresshints\%/@$addresshints/g;
	$string =~ s/\%mailaddress\%/$mailaddress/g;
	$string =~ s/\%groupname\%/$groupname/g;
	$string =~ s/\%members\%/$members/g;
	$string =~ s/\%groups\%/$groups/g;
	$string =~ s/\%grouphints\%/@$grouphints/g;

	print "$string\n";
}

sub getuserinfo($) {
	my $username = shift;

	my $pw = getpwnam($username) || die "Failed locating user \"$username\".";
	my $gr = getgrgid($pw->gid) || die "Failed locating primary group of user \"$username\".";

	my ($fullname, $office, $workphone, $homephone, $other) = split /\s*,\s*/, $pw->gecos;
	$office .= '';
	$workphone .= '';
	$homephone .= '';
	$other .= '';
	my @officehints = grep {s/^([\.[:alnum:]_-]+)$/%$1/} split /\s+/, $office;
	my @addresshints = grep {/^([\.[:alnum:]_-]+|\+)@([\.[:alnum:]_-]+)?$/} split /\s+/, $other;
	my $groupname = $gr->name;
	my $members = $gr->members;
	my $groups = join ' ', defined($groups{$username}) ? @{$groups{$username}} : '';
	my @grouphints = grep {s/^([\.[:alnum:]_-]+)$/\@$1/} split /\s+/, $groups;

	return ($fullname, $office, $workphone, $homephone, $other, $groupname, $members, $groups, \@officehints, \@addresshints, \@grouphints);
}

sub get_maildomain {
	open (MAILDOMAINFILE, "<" . $maildomain_path) || die "can’t open $maildomain_path: $!";
	my $string = readline(MAILDOMAINFILE) || die "can’t read $maildomain_path: $!";
	close (MAILDOMAINFILE);

	chomp($string);

	# FIXME: Do some sanity check of the string - ensure a single word, FQDN syntax etc.

	return $string;
}

sub version {
    printf ("localuserinfo version %s\n\n", $version);
    print  <<'EOF';
List fullname and/or other info for each user.

Copyright (C) 2003-2007 Jonas Smedegaard <dr@jones.dk>

EOF
    print <<'EOF';
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License, /usr/share/common-licenses/GPL, for more details.
EOF
}

sub usage {
    print <<"EOF";
localuserinfo USER [USER...]
   List fullname and/or other info for each user

general options:
  --quiet | -q      don't give process information to stdout
  --ignore-badname  ignore non-existing usernames instead of failing
  --help | -h       usage message
  --username        include username
  --fullname | -n   include fullname
  --office          include office
  --officehints     include office hints: words in "office" field
                    with "%" appended
  --workphone       include work phone
  --homephone       include home phone
  --other           include full "other" field
  --addresshints    include address hints: words in "other" field
                    containing "@"
  --mail | -m       include email address: USERNAME\@MAILDOMAIN
  --groupname       include primary group
  --members         include members of primary group
  --groups          include secondary groups
  --grouphints      include group hints: secondary groups with "@"
                    appended
  --custom=TEMPLATE custom template, e.g. '%username% (%fullname%)'
                    available infochunks:
			* username
			* fullname
			  office
			* officehints
			* workphone
			* homephone
			  other
			* addresshints
			* mailaddress
			  groupname
			  members
			  groups
			* grouphint
                    (all marked infosnippets are included by default,
		    in the order listed)
  --version | -v    version number and copyright
EOF
}