summaryrefslogtreecommitdiffstats
path: root/debian/local
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:22:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:22:53 +0000
commitf4b22a2f215f6f80558d9e4075c9de306c8b9953 (patch)
tree05142dd668b11fc304d1c15faa52dee3784f8fa0 /debian/local
parentAdding upstream version 1.5.2. (diff)
downloadpam-f4b22a2f215f6f80558d9e4075c9de306c8b9953.tar.xz
pam-f4b22a2f215f6f80558d9e4075c9de306c8b9953.zip
Adding debian version 1.5.2-6+deb12u1.debian/1.5.2-6+deb12u1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/local')
-rw-r--r--debian/local/Debian-PAM-MiniPolicy145
-rw-r--r--debian/local/common-account26
-rw-r--r--debian/local/common-account.md5sums2
-rw-r--r--debian/local/common-auth26
-rw-r--r--debian/local/common-auth.md5sums3
-rw-r--r--debian/local/common-password34
-rw-r--r--debian/local/common-password.md5sums6
-rw-r--r--debian/local/common-session24
-rw-r--r--debian/local/common-session-noninteractive25
-rw-r--r--debian/local/common-session-noninteractive.md5sums1
-rw-r--r--debian/local/common-session.md5sums3
-rw-r--r--debian/local/other16
-rw-r--r--debian/local/pam-auth-update744
-rw-r--r--debian/local/pam-auth-update.8112
-rw-r--r--debian/local/pam.conf15
-rw-r--r--debian/local/pam_getenv123
16 files changed, 1305 insertions, 0 deletions
diff --git a/debian/local/Debian-PAM-MiniPolicy b/debian/local/Debian-PAM-MiniPolicy
new file mode 100644
index 0000000..8cba0ae
--- /dev/null
+++ b/debian/local/Debian-PAM-MiniPolicy
@@ -0,0 +1,145 @@
+Author: Ben Collins <bcollins@debian.org>
+Modified by: Sam Hartman <hartmans@debian.org>,
+ Steve Langasek <vorlon@debian.org>
+
+Objective: To document a base set of policies regarding PAM (Pluggable
+Authentication Modules) usage in Debian packages.
+
+===========================================================================
+
+In order to have a consistent and stable implementation across packages
+that use PAM, these guidelines will help to avoid some common mistakes and
+be usable as a cross reference for FAQ's.
+
+This document will not go into the details of how to add PAM usage to
+existing code; please read the documentation in the libpam-doc package for
+info on that. However, it does specify behavior needed to make sure PAM
+modules in Debian will work with your application.
+
+==================
+ PAM Applications
+==================
+
+Each application that uses PAM also must contain a file in /etc/pam.d/.
+This file specifies which PAM modules will be used for the common PAM
+functions in that application. There are several notes concerning what
+modules to use in this file. Most commonly, this file should use the
+@include directive to include common-auth, common-account, and
+common-password, and one of either common-session or
+common-session-noninteractive.
+
+The selection of common-session or common-session-noninteractive is based
+on whether the service provides "shell-like" interactive capabilities to
+the user (e.g.: login, ssh, gdm) or is a non-interactive session or a
+session mediated by a structured protocol (e.g.: cron, cups, samba, ppp).
+This allows a service to avoid calling some modules, such as
+pam_ck_connector, that only make sense in an interactive context and should
+be avoided otherwise. It is expected that the modules used for
+noninteractive sessions will always be a subset of those used for
+interactive sessions.
+
+Under some circumstances (such as ftp auth, or auth based on tty) other
+service-specific modules will need to be listed in the service's /etc/pam.d
+file.
+
+Here is an example of a PAM configuration file that just includes the
+common module fragments:
+
+ #
+ # /etc/pam.d/other - specify the PAM fallback behaviour
+ #
+ # Note that this file is used for any unspecified service; for example
+ #if /etc/pam.d/cron specifies no session modules but cron calls
+ #pam_open_session, the session module out of /etc/pam.d/other is
+ #used. If you really want nothing to happen then use pam_permit.so or
+ #pam_deny.so as appropriate.
+
+ # We fall back to the system default in /etc/pam.d/common-*
+ #
+
+ @include common-auth
+ @include common-account
+ @include common-password
+ @include common-session
+
+The name of this file is determined by the call to pam_start() in the
+application source code. The first parameter will be a string containing
+the "service" name (eg. "login", "httpd", etc..). Please make sure that
+the filename coincides with the value of this parameter used in your
+application.
+
+The file should _not_ reference the full path of the modules. It only needs
+to reference the basename (eg. "pam_unix.so"). This will ensure that the
+program continues to work even if the module location changes, since
+libpam itself will resolve the location.
+
+
+Packages which configure their services by default to use modules other than
+those provided by /etc/pam.d/common-* must depend on the package providing
+those modules. E.g., /etc/pam.d/login includes the line:
+
+ session required pam_limits.so
+
+therefore it must depend on libpam-modules, which provides
+/lib/security/pam_limits.so.
+
+Applications need to depend on libpam-runtime to guarantee that
+/etc/pam.d/common-* exist.
+
+Applications that use common-session-noninteractive must depend
+on libpam-runtime for this file.
+
+
+The pam_unix.so module allows programs to authenticate the uid of the
+calling process without being setuid or setgid. NOTE: this means the user
+executing the program; you cannot authenticate other users without suid
+root (root makes sure the NIS and NIS+ works too) or at least sgid shadow
+(won't work in the above cases). Most notably this affects programs like
+apache being able to use PAM since it runs as www-data which has no
+privileges and cannot use pam_unix.so to authenticate other users. On the
+other hand it does allow programs like vlock to authenticate.
+
+The application needs to follow the following rules to make sure PAM
+modules work:
+
+1) Use the same PAM handle for all operations. This means it is not OK to
+call pam_start once for authentication and then later for session
+management. Modules need to be able to store pam_data between entry
+points.
+
+2) The pam_open_session and pam_setcred calls must be made in a parent
+process of the eventual session. They need to be able to influence the
+environment of the session.
+
+3) If you are started as root or have root privs for some other reason,
+pam_open_session and pam_setcred should be called while still root.
+
+4) Implied by 1, make sure that pam_close_session and pam_end are called in
+the same process or a process descended from the execution context as
+pam_open_session and pam_setcred. The pam_close_session call may need
+state stored in the handle by the open session entry point to clean up
+properly. The pam_end call may need to free data (thus influencing system
+state in some cases) allocated in the earlier calls.
+
+
+
+=============
+ PAM Modules
+=============
+
+Separately packaged PAM modules should adhere to a few basic setup rules:
+
+ 1) Packages should use the naming scheme of `libpam-<name>' (eg.
+ libpam-ldap).
+
+ 2) The modules should be located in the directory of the most recent
+ libpam-modules (currently /lib/security).
+
+ 3) The module should be named as pam_<name>.so. The module should not
+ contain a version suffix.
+
+ 4) The module should be linked to libpam (-lpam) when compiled so that
+ proper version dependencies will work.
+
+ 5) Any config files should be located in /etc/security. The filename
+ will be in the form of <name>.conf.
diff --git a/debian/local/common-account b/debian/local/common-account
new file mode 100644
index 0000000..84aa98d
--- /dev/null
+++ b/debian/local/common-account
@@ -0,0 +1,26 @@
+#
+# /etc/pam.d/common-account - authorization settings common to all services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of the authorization modules that define
+# the central access policy for use on the system. The default is to
+# only deny service to users whose accounts are expired in /etc/shadow.
+#
+# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
+# To take advantage of this, it is recommended that you configure any
+# local modules either before or after the default block, and use
+# pam-auth-update to manage selection of other modules. See
+# pam-auth-update(8) for details.
+#
+
+# here are the per-package modules (the "Primary" block)
+$account_primary
+# here's the fallback if no module succeeds
+account requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+account required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+$account_additional
+# end of pam-auth-update config
diff --git a/debian/local/common-account.md5sums b/debian/local/common-account.md5sums
new file mode 100644
index 0000000..047c30e
--- /dev/null
+++ b/debian/local/common-account.md5sums
@@ -0,0 +1,2 @@
+9f04221fe44762047894adeb96ffd069 debian/local/common-account
+3c0c362eaf3421848b679d63fd48c3fa # 1.0.1-6 -
diff --git a/debian/local/common-auth b/debian/local/common-auth
new file mode 100644
index 0000000..ac9d2dd
--- /dev/null
+++ b/debian/local/common-auth
@@ -0,0 +1,26 @@
+#
+# /etc/pam.d/common-auth - authentication settings common to all services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of the authentication modules that define
+# the central authentication scheme for use on the system
+# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
+# traditional Unix authentication mechanisms.
+#
+# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
+# To take advantage of this, it is recommended that you configure any
+# local modules either before or after the default block, and use
+# pam-auth-update to manage selection of other modules. See
+# pam-auth-update(8) for details.
+
+# here are the per-package modules (the "Primary" block)
+$auth_primary
+# here's the fallback if no module succeeds
+auth requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+auth required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+$auth_additional
+# end of pam-auth-update config
diff --git a/debian/local/common-auth.md5sums b/debian/local/common-auth.md5sums
new file mode 100644
index 0000000..48bd359
--- /dev/null
+++ b/debian/local/common-auth.md5sums
@@ -0,0 +1,3 @@
+933d757dcd5974b00619f68955743be7 /etc/pam.d/common-auth
+b58d8e0a6cadbf879df94869cca6be98 /etc/pam.d/common-auth
+8d4fe17e66ba25de16a117035d1396aa # 1.0.1-6 -
diff --git a/debian/local/common-password b/debian/local/common-password
new file mode 100644
index 0000000..676b814
--- /dev/null
+++ b/debian/local/common-password
@@ -0,0 +1,34 @@
+#
+# /etc/pam.d/common-password - password-related modules common to all services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of modules that define the services to be
+# used to change user passwords. The default is pam_unix.
+
+# Explanation of pam_unix options:
+# The "yescrypt" option enables
+#hashed passwords using the yescrypt algorithm, introduced in Debian
+#11. Without this option, the default is Unix crypt. Prior releases
+#used the option "sha512"; if a shadow password hash will be shared
+#between Debian 11 and older releases replace "yescrypt" with "sha512"
+#for compatibility . The "obscure" option replaces the old
+#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage
+#for other options.
+
+# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
+# To take advantage of this, it is recommended that you configure any
+# local modules either before or after the default block, and use
+# pam-auth-update to manage selection of other modules. See
+# pam-auth-update(8) for details.
+
+# here are the per-package modules (the "Primary" block)
+$password_primary
+# here's the fallback if no module succeeds
+password requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+password required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+$password_additional
+# end of pam-auth-update config
diff --git a/debian/local/common-password.md5sums b/debian/local/common-password.md5sums
new file mode 100644
index 0000000..3c33255
--- /dev/null
+++ b/debian/local/common-password.md5sums
@@ -0,0 +1,6 @@
+601ecfbc99fd359877552cb5298087ad /etc/pam.d/common-password
+e5ae8ba8d00083c922d9d82a0432ef78 /etc/pam.d/common-password
+5d518818f1c6c369040b782f7852f53e /etc/pam.d/common-password
+9ba753d0824276b44bcadfee1f87b6bc # 1.0.1-4ubuntu5 - 1.0.1-4ubuntu5.5
+4bd7610f2e85f8ddaef79c7db7cb49eb # 1.0.1-6 - 1.1.0-1
+50fce2113dfda83ac8bdd5a6e706caec # 1.0.1-6ubuntu1 -
diff --git a/debian/local/common-session b/debian/local/common-session
new file mode 100644
index 0000000..d0a229a
--- /dev/null
+++ b/debian/local/common-session
@@ -0,0 +1,24 @@
+#
+# /etc/pam.d/common-session - session-related modules common to all services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of modules that define tasks to be performed
+# at the start and end of interactive sessions.
+#
+# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
+# To take advantage of this, it is recommended that you configure any
+# local modules either before or after the default block, and use
+# pam-auth-update to manage selection of other modules. See
+# pam-auth-update(8) for details.
+
+# here are the per-package modules (the "Primary" block)
+$session_primary
+# here's the fallback if no module succeeds
+session requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+session required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+$session_additional
+# end of pam-auth-update config
diff --git a/debian/local/common-session-noninteractive b/debian/local/common-session-noninteractive
new file mode 100644
index 0000000..1dd1a17
--- /dev/null
+++ b/debian/local/common-session-noninteractive
@@ -0,0 +1,25 @@
+#
+# /etc/pam.d/common-session-noninteractive - session-related modules
+# common to all non-interactive services
+#
+# This file is included from other service-specific PAM config files,
+# and should contain a list of modules that define tasks to be performed
+# at the start and end of all non-interactive sessions.
+#
+# As of pam 1.0.1-6, this file is managed by pam-auth-update by default.
+# To take advantage of this, it is recommended that you configure any
+# local modules either before or after the default block, and use
+# pam-auth-update to manage selection of other modules. See
+# pam-auth-update(8) for details.
+
+# here are the per-package modules (the "Primary" block)
+$session_nonint_primary
+# here's the fallback if no module succeeds
+session requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+session required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+$session_nonint_additional
+# end of pam-auth-update config
diff --git a/debian/local/common-session-noninteractive.md5sums b/debian/local/common-session-noninteractive.md5sums
new file mode 100644
index 0000000..020c2e4
--- /dev/null
+++ b/debian/local/common-session-noninteractive.md5sums
@@ -0,0 +1 @@
+ad2b78ce1498dd637ef36469430b6ac6 # 1.0.1-11 -
diff --git a/debian/local/common-session.md5sums b/debian/local/common-session.md5sums
new file mode 100644
index 0000000..9f06fa1
--- /dev/null
+++ b/debian/local/common-session.md5sums
@@ -0,0 +1,3 @@
+4845c1632b3561a9debe8d59be1b238e /etc/pam.d/common-session
+4a25673e8b36f1805219027d3be02cd2 # 1.0.1-4ubuntu5 - 1.0.1-4ubuntu5.5
+240fb92986c885b327cdb21dd641da8c # 1.0.1-6 -
diff --git a/debian/local/other b/debian/local/other
new file mode 100644
index 0000000..59d776c
--- /dev/null
+++ b/debian/local/other
@@ -0,0 +1,16 @@
+#
+# /etc/pam.d/other - specify the PAM fallback behaviour
+#
+# Note that this file is used for any unspecified service; for example
+#if /etc/pam.d/cron specifies no session modules but cron calls
+#pam_open_session, the session module out of /etc/pam.d/other is
+#used. If you really want nothing to happen then use pam_permit.so or
+#pam_deny.so as appropriate.
+
+# We fall back to the system default in /etc/pam.d/common-*
+#
+
+@include common-auth
+@include common-account
+@include common-password
+@include common-session
diff --git a/debian/local/pam-auth-update b/debian/local/pam-auth-update
new file mode 100644
index 0000000..ac00b1c
--- /dev/null
+++ b/debian/local/pam-auth-update
@@ -0,0 +1,744 @@
+#!/usr/bin/perl -w
+
+# pam-auth-update: update /etc/pam.d/common-* from /usr/share/pam-configs
+#
+# Update the /etc/pam.d/common-* files based on the per-package profiles
+# provided in /usr/share/pam-configs/ taking into consideration user's
+# preferences (as determined via debconf prompting).
+#
+# Written by Steve Langasek <steve.langasek@canonical.com>
+#
+# Copyright (C) 2008 Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 3 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# # 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.
+
+use strict;
+use Debconf::Client::ConfModule ':all';
+use IPC::Open2 'open2';
+
+version('2.0');
+my $capb=capb('backup escape');
+
+my $inputdir = '/usr/share/pam-configs';
+my $template = 'libpam-runtime/profiles';
+my $errtemplate = 'libpam-runtime/conflicts';
+my $overridetemplate = 'libpam-runtime/override';
+my $blanktemplate = 'libpam-runtime/no_profiles_chosen';
+my $titletemplate = 'libpam-runtime/title';
+my $confdir = '/etc/pam.d';
+my $savedir = '/var/lib/pam';
+my (%profiles, @sorted, @enabled, @conflicts, @new, %removals, %to_disable, %to_enable);
+my $force = 0;
+my $package = 0;
+my $priority = 'high';
+my %md5sums = (
+ 'auth' => ['8d4fe17e66ba25de16a117035d1396aa'],
+ 'account' => ['3c0c362eaf3421848b679d63fd48c3fa'],
+ 'password' => [
+ '4d5c92d595a46b69cd61f18feb4c0574',
+ '50fce2113dfda83ac8bdd5a6e706caec',
+ '4bd7610f2e85f8ddaef79c7db7cb49eb',
+ '9ba753d0824276b44bcadfee1f87b6bc',
+ ],
+ 'session' => [
+ 'f297c731a467822cbd86e1283263e8a3',
+ '240fb92986c885b327cdb21dd641da8c',
+ '4a25673e8b36f1805219027d3be02cd2',
+ ],
+ 'session-noninteractive' => [
+ 'ad2b78ce1498dd637ef36469430b6ac6',
+ ],
+);
+my @invalid_modules = ('pam_tally');
+
+# use a '--force' arg to specify that /etc/pam.d should be overwritten;
+# used only on upgrades where the postinst has already determined that the
+# checksums match. Module packages other than libpam-runtime itself must
+# NEVER use this option! Document with big skullses and crossboneses! It
+# needs to be exposed for libpam-runtime because that's the package that
+# decides whether we have a pristine config to be converted, and knows
+# whether the version being upgraded from is one for which the conversion
+# should be done.
+
+while ($#ARGV >= 0) {
+ my $opt = shift;
+ if ($opt eq '--force') {
+ $force = 1;
+ } elsif ($opt eq '--package') {
+ $package = 1;
+ } elsif ($opt eq '--root') {
+ my $rootdir = shift @ARGV;
+ $savedir = "${rootdir}$savedir";
+ $confdir = "${rootdir}$confdir";
+ $inputdir = "${rootdir}$inputdir";
+ } elsif ($opt eq '--remove') {
+ while ($#ARGV >= 0) {
+ last if ($ARGV[0] =~ /^--/);
+ $removals{shift @ARGV} = 1;
+ }
+ # --remove implies --package
+ $package = 1 if (keys(%removals));
+ } elsif ($opt eq '--disable') {
+ while ($#ARGV >= 0) {
+ last if ($ARGV[0] =~ /^--/);
+ $to_disable{shift @ARGV} = 1;
+ }
+ # --disable implies --package
+ $package = 1 if (keys(%to_disable));
+ } elsif ($opt eq '--enable') {
+ while ($#ARGV >= 0) {
+ last if ($ARGV[0] =~ /^--/);
+ $to_enable{shift @ARGV} = 1;
+ }
+ # --enable implies --package
+ $package = 1 if (keys(%to_enable));
+ }
+}
+
+opendir(DIR, $inputdir) || die "could not open config directory: $!";
+while (my $profile = readdir(DIR)) {
+ next if ($profile eq '.' || $profile eq '..' || $profile =~ m/~$/ || $profile =~ m/^#.+#$/);
+ %{$profiles{$profile}} = parse_pam_profile($inputdir . '/' . $profile);
+ if (defined $profiles{$profile}{'disabled'} and $profiles{$profile}{'disabled'}) {
+ delete $profiles{$profile};
+ }
+}
+closedir DIR;
+
+$priority = 'medium' if ($package);
+
+x_loadtemplatefile('/var/lib/dpkg/info/libpam-runtime.templates','libpam-runtime');
+
+# always sort by priority, so we have consistency and don't have to
+# shuffle later
+@sorted = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
+ || $b cmp $a }
+ keys(%profiles);
+# If we're being called for package removal, filter out those options here
+@sorted = grep { !$removals{$_} } @sorted;
+
+subst($template, 'profile_names', join(', ',@sorted));
+subst($template, 'profiles',
+ join(', ', map { $profiles{$_}->{'Name'} } @sorted));
+
+my $diff = diff_profiles($confdir,$savedir);
+
+if ($diff) {
+ @enabled = grep { !$removals{$_} } @{$diff->{'mods'}};
+} else {
+ @enabled = split(/, /,get($template));
+}
+
+# find out what we've seen, so we can ignore those defaults
+my %seen;
+if (-e $savedir . '/seen') {
+ open(SEEN,$savedir . '/seen') or die("open(${savedir}/seen) failed: $!");
+ while (<SEEN>) {
+ chomp;
+ $seen{$_} = 1;
+ }
+ close(SEEN);
+}
+
+# filter out any options that are no longer available for any reason
+@enabled = grep { $profiles{$_} } @enabled;
+
+# add configs to enable
+push(@enabled,
+ grep { $to_enable{$_} } @sorted);
+
+# Disable anything explicitly disabled
+@enabled = grep {!$to_disable{$_} } @enabled;
+# And we've seen anything we disable
+foreach my $i (keys %to_disable) {
+ $seen{$i} = 1;
+}
+
+# an empty module set is an error, so in that case grab all the defaults
+if (!@enabled) {
+ %seen = ();
+ $priority = 'high' unless ($force);
+}
+
+
+# add any previously-unseen configs
+push(@enabled,
+ grep { $profiles{$_}->{'Default'} eq 'yes' && !$seen{$_} } @sorted);
+@enabled = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
+ || $b cmp $a }
+ @enabled;
+my $prev = '';
+@enabled = grep { $_ ne $prev && (($prev) = $_) } @enabled;
+
+# Do we have any new options to show? If not, we shouldn't reprompt the
+# user, at any priority level, unless explicitly called.
+@new = grep { !$seen{$_} } @sorted;
+
+settitle($titletemplate);
+
+# if diff_profiles() fails, and we weren't passed a 'force' argument
+# (because this isn't an upgrade from an old version, or the checksum
+# didn't match, or we're being called by some other module package), prompt
+# the user whether to override. If the user declines (the default), we
+# never again manage this config unless manually called with '--force'.
+if (!$diff && !$force) {
+ input('high',$overridetemplate);
+ go();
+ $force = 1 if (get($overridetemplate) eq 'true');
+}
+
+if (!$diff && !$force) {
+ print STDERR <<EOF;
+
+pam-auth-update: Local modifications to /etc/pam.d/common-*, not updating.
+pam-auth-update: Run pam-auth-update --force to override.
+
+EOF
+ exit;
+}
+
+umask(0022);
+
+do {
+ @conflicts = ();
+
+ if (@new || !$package) {
+ fset($template,'seen','false');
+ }
+ set($template,join(', ', @enabled));
+
+ input($priority,$template);
+ go();
+
+ @enabled = split(/, /, get($template));
+
+ # in case of conflicts, automatically unset the lower priority
+ # item of each pair
+ foreach my $elem (@enabled)
+ {
+ for (my $i=$#enabled; $i >= 0; $i--)
+ {
+ my $conflict = $enabled[$i];
+ if ($profiles{$elem}->{'Conflicts'}->{$conflict}) {
+ splice(@enabled,$i,1);
+ my $desc = $profiles{$elem}->{'Name'}
+ . ', ' . $profiles{$conflict}->{'Name'};
+ push(@conflicts,$desc);
+ }
+ }
+ }
+ if (@conflicts) {
+ subst($errtemplate, 'conflicts', join("\\n", @conflicts));
+ input('high',$errtemplate);
+ }
+ set($template, join(', ', @enabled));
+ if (!@enabled) {
+ input('high',$blanktemplate);
+ # we can only end up here by user error, but give them another
+ # shot at selecting a correct config anyway.
+ fset($template,'seen','false');
+ }
+} while (@conflicts || !@enabled);
+
+# the decision has been made about what configs to use, so even if
+# something fails after this, we shouldn't go munging the default
+# options again. Save the list of known configs to /var/lib/pam.
+open(SEEN,"> $savedir/seen") or die("open(${savedir}/seen) failed: $!");
+for my $i (@sorted) {
+ print SEEN "$i\n";
+}
+close(SEEN) or die("close(${savedir}/seen) failed: $!");
+
+# @enabled now contains our list of profiles to use for piecing together
+# a config
+# we have:
+# - templates into which we insert the specialness
+# - magic comments denoting the beginning and end of our managed block;
+# looking at only the functional config lines would potentially let us
+# handle more cases, at the expense of much greater complexity, so
+# pass on this at least for the first round
+# - a representation of the autogenerated config stored in /var/lib/pam,
+# that we can diff against in order to account for changed options or
+# manually dropped modules
+# - a hash describing the local modifications the user has made to the
+# config; these are always preserved unless manually overridden with
+# the --force option
+
+write_profiles(\%profiles, \@enabled, $confdir, $savedir, $diff, $force);
+
+
+# take a single line from a stock config, and merge it with the
+# information about local admin edits
+sub merge_one_line
+{
+ my ($line,$diff,$count) = @_;
+ my (@opts,$modline);
+
+ my ($adds,$removes);
+
+ $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
+
+ @opts = split(/\s+/,$3);
+ $modline = $1;
+ $modline =~ s/end/$count/g;
+ if ($diff) {
+ my $mod = $modline;
+ $mod =~ s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
+ $adds = \%{$diff->{'add'}{$mod}};
+ $removes = \%{$diff->{'remove'}{$mod}};
+ } else {
+ $adds = $removes = undef;
+ }
+
+ for (my $i = 0; $i <= $#opts; $i++) {
+ if ($adds->{$opts[$i]}) {
+ delete $adds->{$opts[$i]};
+ }
+ if ($removes->{$opts[$i]}) {
+ splice(@opts,$i,1);
+ $i--;
+ }
+ }
+ return $modline . " " . join(' ',@opts,sort keys(%{$adds})) . "\n";
+}
+
+# return the lines for a given config name, type, and position in the stack
+sub lines_for_module_and_type
+{
+ my ($profiles, $mod, $type, $modpos) = @_;
+ if ($modpos == 0 && $profiles->{$mod}{$type . '-Initial'}) {
+ return $profiles->{$mod}{$type . '-Initial'};
+ }
+ return $profiles->{$mod}{$type};
+}
+
+# create a single PAM config from the indicated template and selections,
+# writing to a new file
+sub create_from_template
+{
+ my($template,$dest,$profiles,$enabled,$diff,$type) = @_;
+ my $state = 0;
+ my $uctype = ucfirst($type);
+ $type =~ s/-noninteractive//;
+
+ open(INPUT,$template) || return 0;
+ open(OUTPUT,">$dest") || return 0;
+
+ while (<INPUT>) {
+ if ($state == 1) {
+ if (/^# here's the fallback if no module succeeds/) {
+ print OUTPUT;
+ $state++;
+ }
+ next;
+ }
+ if ($state == 3) {
+ if (/^# end of pam-auth-update config/) {
+ print OUTPUT;
+ $state++;
+ }
+ next;
+ }
+
+ print OUTPUT;
+
+ my ($pattern,$val);
+ if ($state == 0) {
+ $pattern = '^# here are the per-package modules \(the "Primary" block\)';
+ $val = 'Primary';
+ } elsif ($state == 2) {
+ $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
+ $val = 'Additional';
+ } else {
+ next;
+ }
+
+ if (/$pattern/) {
+ my $i = 0;
+ my $count = 0;
+ # first we need to get a count of lines that we're
+ # going to output, so we can fix up the jumps correctly
+ for my $mod (@{$enabled}) {
+ my $output;
+ next if (!$profiles->{$mod}{$uctype . '-Type'});
+ next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
+ $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
+ # bypasses a perl warning about @_, sigh
+ my @tmparr = split("\n+",$output);
+ $count += @tmparr;
+ }
+
+ # in case anything tries to jump in the 'additional'
+ # block, let's try not to jump off the stack...
+ $count-- if ($val eq 'Additional');
+
+ # no primary block, so output a stock pam_permit line
+ # to keep the stack intact
+ if ($val eq 'Primary' && $count == 0)
+ {
+ print OUTPUT "$type\t[default=1]\t\t\tpam_permit.so\n";
+ }
+
+ $i = 0;
+ for my $mod (@{$enabled}) {
+ my $output;
+ my @output;
+ next if (!$profiles->{$mod}{$uctype . '-Type'});
+ next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
+ $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
+ for my $line (split("\n",$output)) {
+ $line = merge_one_line($line,$diff,
+ $count);
+ print OUTPUT "$type\t$line";
+ $count--;
+ }
+ }
+ $state++;
+ }
+ }
+ close(INPUT);
+ close(OUTPUT) or die("close($dest) failed: $!");
+
+ if ($state < 4) {
+ unlink($dest);
+ return 0;
+ }
+ return 1;
+}
+
+# take a template file, strip out everything between the markers, and
+# return the md5sum of the remaining contents. Used for testing for
+# local modifications of the boilerplate.
+sub get_template_md5sum
+{
+ my($template) = @_;
+ my $state = 0;
+
+ open(INPUT,$template) || return '';
+ my($md5sum_fd,$output_fd);
+ my $pid = open2($md5sum_fd, $output_fd, 'md5sum');
+ return '' if (!$pid);
+
+ while (<INPUT>) {
+ if ($state == 1) {
+ if (/^# here's the fallback if no module succeeds/) {
+ print $output_fd $_;
+ $state++;
+ }
+ next;
+ }
+ if ($state == 3) {
+ if (/^# end of pam-auth-update config/) {
+ print $output_fd $_;
+ $state++;
+ }
+ next;
+ }
+
+ print $output_fd $_;
+
+ my ($pattern,$val);
+ if ($state == 0) {
+ $pattern = '^# here are the per-package modules \(the "Primary" block\)';
+ } elsif ($state == 2) {
+ $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
+ } else {
+ next;
+ }
+
+ if (/$pattern/) {
+ $state++;
+ }
+ }
+ close(INPUT);
+ close($output_fd);
+ my $md5sum = <$md5sum_fd>;
+ close($md5sum_fd);
+ waitpid $pid, 0;
+
+ $md5sum = (split(/\s+/,$md5sum))[0];
+ return $md5sum;
+}
+
+# merge a set of module declarations into a set of new config files,
+# using the information returned from diff_profiles().
+sub write_profiles
+{
+ my($profiles,$enabled,$confdir,$savedir,$diff,$force) = @_;
+
+ if (! -d $savedir) {
+ mkdir($savedir);
+ }
+
+ # because we can't atomically replace both /var/lib/pam/$foo and
+ # /etc/pam.d/common-$foo at the same time, take steps to make this
+ # somewhat robust
+ for my $type ('auth','account','password','session',
+ 'session-noninteractive')
+ {
+ my $target = $confdir . '/common-' . $type;
+ my $template = $target;
+ my $dest = $template . '.pam-new';
+
+ my $diff = $diff;
+ if ($diff) {
+ $diff = \%{$diff->{$type}};
+ }
+
+ # Detect if the template is unmodified, and if so, use
+ # the version from /usr/share. Depends on knowing the
+ # md5sums of the originals.
+ my $md5sum = get_template_md5sum($template);
+ for my $i (@{$md5sums{$type}}) {
+ if ($md5sum eq $i) {
+ $template = '/usr/share/pam/common-' . $type;
+ last;
+ }
+ }
+
+ # first, write out the new config
+ if (!create_from_template($template,$dest,$profiles,$enabled,
+ $diff,$type))
+ {
+ if (!$force) {
+ return 0;
+ }
+ $template = '/usr/share/pam/common-' . $type;
+ if (!create_from_template($template,$dest,$profiles,
+ $enabled,$diff,$type))
+ {
+ return 0;
+ }
+ }
+
+ # then write out the saved config
+ if (!open(OUTPUT, "> $savedir/$type.new")) {
+ unlink($dest);
+ return 0;
+ }
+ my $i = 0;
+ my $uctype = ucfirst($type);
+ for my $mod (@{$enabled}) {
+ my $output;
+ next if (!$profiles->{$mod}{$uctype . '-Type'});
+ next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Additional');
+
+ $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
+ if ($output) {
+ print OUTPUT "Module: $mod\n";
+ print OUTPUT $output . "\n";
+ }
+ }
+
+ # no primary block, so output a stock pam_permit line
+ if ($i == 0)
+ {
+ print OUTPUT "Module: null\n";
+ print OUTPUT "[default=1]\t\t\tpam_permit.so\n";
+ }
+
+ $i = 0;
+ for my $mod (@{$enabled}) {
+ my $output;
+ next if (!$profiles->{$mod}{$uctype . '-Type'});
+ next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Primary');
+
+ $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
+ if ($output) {
+ print OUTPUT "Module: $mod\n";
+ print OUTPUT $output . "\n";
+ }
+ }
+
+ close(OUTPUT) or die("close($dest) failed: $!");
+
+ # then do the renames, back-to-back
+ # we have to use system because File::Copy is in
+ # perl-modules, not perl-base
+ if (-e $target && $force) {
+ system('cp','-f',$target,$target . '.pam-old') == 0
+ or die("cp -f ${target} ${target}.pam.old failed");
+ }
+ rename($dest,$target)
+ or die("rename($dest, $target) failed: $!");
+ rename("$savedir/${type}.new","$savedir/$type")
+ or die("rename(${savedir}/${type}.new, ${savedir}/${type}) failed: $!");
+ }
+
+ # at the end of a successful write, reset the 'seen' flag and the
+ # value of the debconf override question.
+ fset($overridetemplate,'seen','false');
+ set($overridetemplate,'false');
+}
+
+# reconcile the current config in /etc/pam.d with the saved ones in
+# /var/lib/pam; returns a hash of profile names and the corresponding
+# options that should be added/removed relative to the stock config.
+# returns false if any of the markers are missing that permit a merge,
+# or on any other failure.
+sub diff_profiles
+{
+ my ($sourcedir,$savedir) = @_;
+ my (%diff);
+
+ @{$diff{'mods'}} = ();
+ # Load the saved config from /var/lib/pam, then iterate through all
+ # lines in the current config that are in the managed block.
+ # If anything fails here, just return immediately since we then
+ # have nothing to merge; instead, the caller will decide later
+ # whether to force an overwrite.
+ for my $type ('auth','account','password','session',
+ 'session-noninteractive')
+ {
+ my (@saved,$modname);
+
+ open(SAVED,$savedir . '/' . $type) || return 0;
+ while (<SAVED>) {
+ if (/^Module: (.*)/) {
+ $modname = $1;
+ next;
+ }
+ chomp;
+ # trim out the destination of any jumps; this saves
+ # us from having to re-parse everything just to fix
+ # up the jump lengths, when changes to these will
+ # already show up as inconsistencies elsewhere
+ s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
+ s/(\[.*)end(.*\])/$1$2/g;
+ my (@temp) = ($modname,$_);
+ push(@saved,\@temp);
+ }
+ close(SAVED);
+
+ my $state = 0;
+ my (@prev_opts,$curmod);
+ my $realtype = $type;
+ $realtype =~ s/-noninteractive//;
+
+ open(CURRENT,$sourcedir . '/common-' . $type) || return 0;
+ while (<CURRENT>) {
+ if ($state == 0) {
+ $state = 1
+ if (/^# here are the per-package modules \(the "Primary" block\)/);
+ next;
+ }
+ if ($state == 1) {
+ s/^$realtype\s+//;
+ if (/^# here's the fallback if no module succeeds/) {
+ $state = 2;
+ next;
+ }
+ }
+ if ($state == 2) {
+ $state = 3
+ if (/^# and here are more per-package modules \(the "Additional" block\)/);
+ next;
+ }
+ if ($state == 3) {
+ last if (/^# end of pam-auth-update config/);
+ s/^$realtype\s+//;
+ }
+
+ my $found = 0;
+ my $curopts;
+ while (!$found && $#saved >= 0) {
+ my $line;
+ ($modname,$line) = @{$saved[0]};
+ shift(@saved);
+ $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
+ @prev_opts = split(/\s+/,$3);
+ $curmod = $1;
+ # FIXME: the key isn't derived from the config
+ # name, so collisions are possible if more
+ # than one config references the same module
+
+ $_ =~ s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
+ # check if this is a match for the current line
+ if ($_ =~ /^\Q$curmod\E\s*(.*)$/) {
+ $found = 1;
+ $curopts = $1;
+ push(@{$diff{'mods'}},$modname);
+ }
+ }
+
+ # there's a line in the live config that doesn't
+ # correspond to anything from the saved config.
+ # treat this as a failure; it's very error-prone
+ # to decide what to do with an added line that
+ # didn't come from a package.
+ return 0 if (!$found);
+
+ for my $opt (split(/\s+/,$curopts)) {
+ my $found = 0;
+ for (my $i = 0; $i <= $#prev_opts; $i++) {
+ if ($prev_opts[$i] eq $opt) {
+ $found = 1;
+ splice(@prev_opts,$i,1);
+ }
+ }
+ $diff{$type}{'add'}{$curmod}{$opt} = 1 if (!$found);
+ }
+ for my $opt (@prev_opts) {
+ $diff{$type}{'remove'}{$curmod}{$opt} = 1;
+ }
+ }
+ close(CURRENT);
+
+ # we couldn't parse the config, so the merge fails
+ return 0 if ($state < 3);
+ }
+ return \%diff;
+}
+
+# simple function to parse a provided config file, in pseudo-RFC822
+# format,
+sub parse_pam_profile
+{
+ my ($profile) = $_[0];
+ my $fieldname;
+ my %profile;
+ open(PROFILE, $profile) || die "could not read profile $profile: $!";
+ while (<PROFILE>) {
+ if (/^(\S+):\s+(.*)\s*$/) {
+ $fieldname = $1;
+ # compatibility with the first implementation round;
+ # "Auth-Final" is now just called "Auth"
+ $fieldname =~ s/-Final$//;
+ if ($fieldname eq 'Conflicts') {
+ foreach my $elem (split(/, /, $2)) {
+ $profile{'Conflicts'}->{$elem} = 1;
+ }
+ } else {
+ $profile{$fieldname} = $2;
+ }
+ } else {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ $profile{$fieldname} .= "\n$_" if ($_);
+ if (grep { $profile{$fieldname} =~ /$_/} @invalid_modules) {
+ $profile{'disabled'} = 1;
+ }
+ $profile{$fieldname} =~ s/^[\n\s]+//;
+ }
+ }
+ close(PROFILE);
+ if (!defined($profile{'Session-Interactive-Only'})) {
+ $profile{'Session-noninteractive-Type'} = $profile{'Session-Type'};
+ $profile{'Session-noninteractive'} = $profile{'Session'};
+ $profile{'Session-noninteractive-Initial'} = $profile{'Session-Initial'};
+ }
+ return %profile;
+}
diff --git a/debian/local/pam-auth-update.8 b/debian/local/pam-auth-update.8
new file mode 100644
index 0000000..a31ec92
--- /dev/null
+++ b/debian/local/pam-auth-update.8
@@ -0,0 +1,112 @@
+.\" Copyright (C) 2008 Canonical Ltd.
+.\"
+.\" Author: Steve Langasek <steve.langasek@canonical.com>
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of version 3 of the GNU General Public License as
+.\" published by the Free Software Foundation.
+.\"
+.\" .\" 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.
+.TH "PAM\-AUTH\-UPDATE" "8" "08/23/2008" "Debian"
+.SH NAME
+pam\-auth\-update - manage PAM configuration using packaged profiles
+.SH SYNOPSIS
+.B pam\-auth\-update
+.RB [ \-\-package " [" \-\-remove
+.IR profile " [" profile\fR... "]]]"
+.RB [ \-\-force ]
+.RB [ \-\-enable
+.IR profile " [" profile\fR... "]]"
+.RB [ \-\-disable
+.IR profile " [" profile\fR... "]]"
+.SH DESCRIPTION
+.I pam\-auth\-update
+is a utility that permits configuring the central authentication policy
+for the system using pre-defined profiles as supplied by PAM module
+packages.
+Profiles shipped in the
+.I /usr/share/pam\-configs/
+directory specify the modules, with options, to enable; the preferred
+ordering with respect to other profiles; and whether a profile should be
+enabled by default.
+Packages providing PAM modules register their profiles at install time
+by calling
+.BR "pam\-auth\-update \-\-package" .
+Selection of profiles is done using the standard debconf interface.
+The profile selection question will be asked at `medium' priority when
+packages are added or removed, so no user interaction is required by
+default.
+Users may invoke
+.B pam\-auth\-update
+directly to change their authentication configuration.
+.PP
+The script makes every effort to respect local changes to
+.IR "/etc/pam.d/common-*".
+Local modifications to the list of module options will be preserved, and
+additions of modules within the managed portion of the stack will cause
+.B pam\-auth\-update
+to treat the config files as locally modified and not make further
+changes to the config files unless given the
+.B \-\-force
+option.
+.PP
+If the user specifies that
+.B pam\-auth\-update
+should override local configuration changes, the locally-modified files
+will be saved in
+.I /etc/pam.d/
+with a suffix of
+.IR "\.pam\-old" .
+.SH OPTIONS
+.TP
+.B \-\-package
+Indicate that the caller is a package maintainer script; lowers the
+priority of debconf questions to `medium' so that the user is not
+prompted by default.
+.TP
+.B \-\-disable \fIprofile \fR[\fIprofile\fR...]
+Disable the specified profiles in system configuration. This can be used from system administration scripts to disable profiles.
+.TP
+.B \-\-enable \fIprofile \fR[\fIprofile\fR...]
+Enable the specified profiles in system configuration. This is used to
+enable profiles that are not on by default.
+.TP
+.B \-\-remove \fIprofile \fR[\fIprofile\fR...]
+Remove the specified profiles from the system configuration.
+.B pam\-auth\-update \-\-remove
+should be used to remove profiles from the configuration before the
+modules they reference are removed from disk, to ensure that PAM is in a
+consistent and usable state at all times during package upgrades or
+removals.
+.TP
+.B \-\-force
+Overwrite the current PAM configuration, without prompting.
+This option
+.B must not
+be used by package maintainer scripts; it is intended for use by
+administrators only.
+.SH FILES
+.PP
+.I /etc/pam.d/common\-*
+.RS 4
+Global configuration of PAM, affecting all installed services.
+.RE
+.PP
+.I /usr/share/pam\-configs/
+.RS 4
+Package-supplied authentication profiles.
+.RE
+.SH AUTHOR
+Steve Langasek <steve.langasek@canonical.com>
+.SH COPYRIGHT
+Copyright (C) 2008 Canonical Ltd.
+.SH "SEE ALSO"
+PAM(7), pam.d(5), debconf(7)
diff --git a/debian/local/pam.conf b/debian/local/pam.conf
new file mode 100644
index 0000000..3eeb72d
--- /dev/null
+++ b/debian/local/pam.conf
@@ -0,0 +1,15 @@
+# ---------------------------------------------------------------------------#
+# /etc/pam.conf #
+# ---------------------------------------------------------------------------#
+#
+# NOTE
+# ----
+#
+# NOTE: Most program use a file under the /etc/pam.d/ directory to setup their
+# PAM service modules. This file is used only if that directory does not exist.
+# ---------------------------------------------------------------------------#
+
+# Format:
+# serv. module ctrl module [path] ...[args..] #
+# name type flag #
+
diff --git a/debian/local/pam_getenv b/debian/local/pam_getenv
new file mode 100644
index 0000000..e409c3e
--- /dev/null
+++ b/debian/local/pam_getenv
@@ -0,0 +1,123 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+pam_getenv - get environment variables from /etc/environment
+
+=head1 SYNOPSIS
+
+pam_getenv B<[-l] [-s]> I<env_var>
+
+=head1 DESCRIPTION
+
+This tool will print out the value of I<env_var> from F</etc/environment>. It will attempt to expand environment variable references in the definition of I<env_var> but will fail if PAM items are expanded.
+
+The B<-l> option indicates the script should return an environment variable related to default locale information.
+
+The B<-s> option indicates that the script should return an
+system default environment variable.
+
+Currently neither the B<-l> or B<-s> options do anything. They are
+included because future versions of Debian may have a separate
+repository for the initial environment used by init scripts and for
+system locale information. These options will allow this script to be
+a stable interface even in that environment.
+
+=cut
+
+# Copyright 2004 by Sam Hartman
+# This script may be copied under the terms of the GNU GPL
+# version 2, or at your option any later version.
+
+use strict;
+use vars qw(*CONFIGFILE *ENVFILE);
+
+sub read_line($) {
+ my $fh = shift;
+ my $line;
+ local $_;
+ line: while (<$fh>) {
+ chomp;
+ s/^\s+//;
+s/\#.*$//;
+ next if $_ eq "";
+ if (s/\\\s*$//) {
+ $line .= $_;
+ next line;
+ }
+
+ $line .= $_;
+ last;
+ }
+ $line;
+
+}
+
+
+sub parse_line($) {
+ my $var;
+ my (%x, @x);
+ local $_ = shift;
+ return undef unless defined $_ and s/(\S+)\s//;
+ $var->{Name} = $1;
+ s/^\s*//;
+ @x = split(/=([^"\s]\S*|"[^"]*")\s*/, $_);
+ unless (scalar(@x)%2 == 0) {
+ push @x, undef;
+ }
+ %x = @x;
+ @{$var}{"Default", "Override"} =
+ @x{"DEFAULT", "OVERRIDE"};
+ $var;
+}
+
+sub expand_val($) {
+ my ($val) = @_;
+return undef unless $val;
+ die "Cannot handle PAM items\n" if /(?<!\\)\@/;
+ $val =~ s/(?<!\\)\$\{([^}]+)\}/$ENV{$1}||""/eg;
+ return $val;
+}
+
+my $lookup;
+
+while ($_ = shift) {
+ next if $_ eq "-s";
+ next if $_ eq "-l";
+ $lookup = $_;
+ last;
+}
+unless (defined $lookup) {
+ die "Usage: pam_getenv [-l] [-s] env_var\n";
+}
+
+my %allvars;
+
+open (CONFIGFILE, "/etc/security/pam_env.conf")
+ or die "Cannot open environment file: $!\n";
+
+while (my $var = parse_line(read_line(\*CONFIGFILE))) {
+ my $val;
+ unless ($val = expand_val($var->{Override})) {
+ $val = expand_val($var->{Default});
+ }
+ $allvars{$var->{Name}} = $val;
+}
+
+if (open (ENVFILE, "/etc/environment")) {
+ while (my $line = read_line(\*ENVFILE)) {
+ $line =~ s/^export //;
+ $line =~ /(.*?)=(.+)/ or next;
+ my ($var, $val) = ($1, $2);
+ # This is bizarre logic (" and ' match each other, quotes are only
+ # significant at the start and end of the string, and the trailing quote
+ # may be omitted), but it's what pam_env does.
+ $val =~ s/^["'](.*?)["']?$/$1/;
+ $allvars{$var} = $val;
+ }
+}
+
+if (exists $allvars{$lookup}) {
+ print $allvars{$lookup}, "\n";
+ exit(0);
+}