From ea488580c42e8918445a945484de3c8a5addc761 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 20 Jun 2000 22:10:38 +0000 Subject: Initial revision --- modules/Makefile | 70 ++ modules/README | 55 ++ modules/dont_makefile | 19 + modules/download-all | 30 + modules/pam_access/.cvsignore | 1 + modules/pam_access/Makefile | 106 ++++ modules/pam_access/README | 40 ++ modules/pam_access/access.conf | 52 ++ modules/pam_access/install_conf | 46 ++ modules/pam_access/pam_access.c | 424 +++++++++++++ modules/pam_cracklib/.cvsignore | 1 + modules/pam_cracklib/Makefile | 118 ++++ modules/pam_cracklib/README | 21 + modules/pam_cracklib/pam_cracklib.c | 776 +++++++++++++++++++++++ modules/pam_deny/.cvsignore | 1 + modules/pam_deny/Makefile | 131 ++++ modules/pam_deny/README | 4 + modules/pam_deny/pam_deny.c | 100 +++ modules/pam_env/.cvsignore | 1 + modules/pam_env/Makefile | 111 ++++ modules/pam_env/README | 72 +++ modules/pam_env/install_conf | 46 ++ modules/pam_env/pam_env.c | 839 ++++++++++++++++++++++++ modules/pam_env/pam_env.conf-example | 72 +++ modules/pam_filter/.cvsignore | 2 + modules/pam_filter/.upperLOWER | 1 + modules/pam_filter/Makefile | 140 ++++ modules/pam_filter/README | 94 +++ modules/pam_filter/include/pam_filter.h | 32 + modules/pam_filter/pam_filter.c | 738 +++++++++++++++++++++ modules/pam_filter/upperLOWER/.cvsignore | 1 + modules/pam_filter/upperLOWER/Makefile | 64 ++ modules/pam_filter/upperLOWER/upperLOWER.c | 174 +++++ modules/pam_ftp/.cvsignore | 1 + modules/pam_ftp/Makefile | 88 +++ modules/pam_ftp/README | 18 + modules/pam_ftp/pam_ftp.c | 299 +++++++++ modules/pam_group/.cvsignore | 1 + modules/pam_group/Makefile | 98 +++ modules/pam_group/group.conf | 60 ++ modules/pam_group/install_conf | 46 ++ modules/pam_group/pam_group.c | 843 ++++++++++++++++++++++++ modules/pam_issue/.cvsignore | 1 + modules/pam_issue/Makefile | 86 +++ modules/pam_issue/pam_issue.c | 265 ++++++++ modules/pam_lastlog/.cvsignore | 1 + modules/pam_lastlog/Makefile | 112 ++++ modules/pam_lastlog/pam_lastlog.c | 463 ++++++++++++++ modules/pam_limits/.cvsignore | 1 + modules/pam_limits/Makefile | 98 +++ modules/pam_limits/README | 87 +++ modules/pam_limits/install_conf | 46 ++ modules/pam_limits/limits.skel | 42 ++ modules/pam_limits/pam_limits.c | 635 +++++++++++++++++++ modules/pam_listfile/.cvsignore | 1 + modules/pam_listfile/Makefile | 84 +++ modules/pam_listfile/README | 25 + modules/pam_listfile/pam_listfile.c | 437 +++++++++++++ modules/pam_mail/.cvsignore | 1 + modules/pam_mail/Makefile | 113 ++++ modules/pam_mail/README | 17 + modules/pam_mail/pam_mail.c | 504 +++++++++++++++ modules/pam_mkhomedir/.cvsignore | 1 + modules/pam_mkhomedir/Makefile | 90 +++ modules/pam_mkhomedir/pam_mkhomedir.c | 370 +++++++++++ modules/pam_motd/.cvsignore | 1 + modules/pam_motd/Makefile | 90 +++ modules/pam_motd/pam_motd.c | 119 ++++ modules/pam_nologin/.cvsignore | 1 + modules/pam_nologin/Makefile | 86 +++ modules/pam_nologin/README | 12 + modules/pam_nologin/pam_nologin.c | 130 ++++ modules/pam_permit/.cvsignore | 1 + modules/pam_permit/Makefile | 132 ++++ modules/pam_permit/README | 4 + modules/pam_permit/pam_permit.c | 128 ++++ modules/pam_pwdb/.cvsignore | 2 + modules/pam_pwdb/BUGS | 14 + modules/pam_pwdb/CHANGELOG | 10 + modules/pam_pwdb/Makefile | 193 ++++++ modules/pam_pwdb/README | 41 ++ modules/pam_pwdb/TODO | 34 + modules/pam_pwdb/bigcrypt.-c | 114 ++++ modules/pam_pwdb/md5.c | 270 ++++++++ modules/pam_pwdb/md5.h | 34 + modules/pam_pwdb/md5_crypt.c | 153 +++++ modules/pam_pwdb/pam_pwdb.c | 256 ++++++++ modules/pam_pwdb/pam_unix_acct.-c | 298 +++++++++ modules/pam_pwdb/pam_unix_auth.-c | 131 ++++ modules/pam_pwdb/pam_unix_md.-c | 73 +++ modules/pam_pwdb/pam_unix_passwd.-c | 404 ++++++++++++ modules/pam_pwdb/pam_unix_pwupd.-c | 260 ++++++++ modules/pam_pwdb/pam_unix_sess.-c | 118 ++++ modules/pam_pwdb/pwdb_chkpwd.c | 204 ++++++ modules/pam_pwdb/support.-c | 938 +++++++++++++++++++++++++++ modules/pam_radius/.cvsignore | 1 + modules/pam_radius/Makefile | 99 +++ modules/pam_radius/README | 58 ++ modules/pam_radius/pam_radius.c | 193 ++++++ modules/pam_radius/pam_radius.h | 38 ++ modules/pam_rhosts/.cvsignore | 1 + modules/pam_rhosts/Makefile | 98 +++ modules/pam_rhosts/README | 57 ++ modules/pam_rhosts/pam_rhosts_auth.c | 829 ++++++++++++++++++++++++ modules/pam_rootok/.cvsignore | 1 + modules/pam_rootok/Makefile | 117 ++++ modules/pam_rootok/README | 18 + modules/pam_rootok/pam_rootok.c | 109 ++++ modules/pam_securetty/.cvsignore | 1 + modules/pam_securetty/Makefile | 83 +++ modules/pam_securetty/README | 9 + modules/pam_securetty/pam_securetty.c | 191 ++++++ modules/pam_shells/.cvsignore | 1 + modules/pam_shells/Makefile | 84 +++ modules/pam_shells/README | 10 + modules/pam_shells/pam_shells.c | 133 ++++ modules/pam_stress/.cvsignore | 1 + modules/pam_stress/Makefile | 115 ++++ modules/pam_stress/README | 66 ++ modules/pam_stress/pam_stress.c | 565 +++++++++++++++++ modules/pam_tally/.cvsignore | 2 + modules/pam_tally/Makefile | 109 ++++ modules/pam_tally/README | 95 +++ modules/pam_tally/faillog.h | 55 ++ modules/pam_tally/pam_tally.c | 689 ++++++++++++++++++++ modules/pam_tally/pam_tally_app.c | 7 + modules/pam_time/.cvsignore | 1 + modules/pam_time/Makefile | 98 +++ modules/pam_time/README | 43 ++ modules/pam_time/install_conf | 46 ++ modules/pam_time/pam_time.c | 612 ++++++++++++++++++ modules/pam_time/time.conf | 64 ++ modules/pam_unix/.cvsignore | 4 + modules/pam_unix/CHANGELOG | 55 ++ modules/pam_unix/Makefile | 155 +++++ modules/pam_unix/README | 39 ++ modules/pam_unix/bigcrypt.c | 119 ++++ modules/pam_unix/lckpwdf.-c | 117 ++++ modules/pam_unix/md5.c | 256 ++++++++ modules/pam_unix/md5.h | 35 + modules/pam_unix/md5_crypt.c | 149 +++++ modules/pam_unix/need_nsl.sh | 7 + modules/pam_unix/pam_unix_acct.c | 210 ++++++ modules/pam_unix/pam_unix_auth.c | 220 +++++++ modules/pam_unix/pam_unix_passwd.c | 985 +++++++++++++++++++++++++++++ modules/pam_unix/pam_unix_sess.c | 138 ++++ modules/pam_unix/support.c | 821 ++++++++++++++++++++++++ modules/pam_unix/support.h | 139 ++++ modules/pam_unix/unix_chkpwd.c | 324 ++++++++++ modules/pam_unix/yppasswd.h | 51 ++ modules/pam_unix/yppasswd_xdr.c | 41 ++ modules/pam_userdb/.cvsignore | 1 + modules/pam_userdb/Makefile | 81 +++ modules/pam_userdb/README | 30 + modules/pam_userdb/conv.c | 125 ++++ modules/pam_userdb/create.pl | 23 + modules/pam_userdb/libdbfound.sh | 15 + modules/pam_userdb/pam_userdb.c | 302 +++++++++ modules/pam_userdb/pam_userdb.h | 61 ++ modules/pam_warn/.cvsignore | 1 + modules/pam_warn/Makefile | 102 +++ modules/pam_warn/README | 26 + modules/pam_warn/pam_warn.c | 132 ++++ modules/pam_wheel/.cvsignore | 1 + modules/pam_wheel/Makefile | 86 +++ modules/pam_wheel/README | 33 + modules/pam_wheel/pam_wheel.c | 275 ++++++++ modules/register_static | 49 ++ 168 files changed, 23474 insertions(+) create mode 100644 modules/Makefile create mode 100644 modules/README create mode 100644 modules/dont_makefile create mode 100755 modules/download-all create mode 100644 modules/pam_access/.cvsignore create mode 100644 modules/pam_access/Makefile create mode 100644 modules/pam_access/README create mode 100644 modules/pam_access/access.conf create mode 100755 modules/pam_access/install_conf create mode 100644 modules/pam_access/pam_access.c create mode 100644 modules/pam_cracklib/.cvsignore create mode 100644 modules/pam_cracklib/Makefile create mode 100644 modules/pam_cracklib/README create mode 100644 modules/pam_cracklib/pam_cracklib.c create mode 100644 modules/pam_deny/.cvsignore create mode 100644 modules/pam_deny/Makefile create mode 100644 modules/pam_deny/README create mode 100644 modules/pam_deny/pam_deny.c create mode 100644 modules/pam_env/.cvsignore create mode 100644 modules/pam_env/Makefile create mode 100644 modules/pam_env/README create mode 100755 modules/pam_env/install_conf create mode 100644 modules/pam_env/pam_env.c create mode 100644 modules/pam_env/pam_env.conf-example create mode 100644 modules/pam_filter/.cvsignore create mode 100644 modules/pam_filter/.upperLOWER create mode 100644 modules/pam_filter/Makefile create mode 100644 modules/pam_filter/README create mode 100644 modules/pam_filter/include/pam_filter.h create mode 100644 modules/pam_filter/pam_filter.c create mode 100644 modules/pam_filter/upperLOWER/.cvsignore create mode 100644 modules/pam_filter/upperLOWER/Makefile create mode 100644 modules/pam_filter/upperLOWER/upperLOWER.c create mode 100644 modules/pam_ftp/.cvsignore create mode 100644 modules/pam_ftp/Makefile create mode 100644 modules/pam_ftp/README create mode 100644 modules/pam_ftp/pam_ftp.c create mode 100644 modules/pam_group/.cvsignore create mode 100644 modules/pam_group/Makefile create mode 100644 modules/pam_group/group.conf create mode 100755 modules/pam_group/install_conf create mode 100644 modules/pam_group/pam_group.c create mode 100644 modules/pam_issue/.cvsignore create mode 100644 modules/pam_issue/Makefile create mode 100644 modules/pam_issue/pam_issue.c create mode 100644 modules/pam_lastlog/.cvsignore create mode 100644 modules/pam_lastlog/Makefile create mode 100644 modules/pam_lastlog/pam_lastlog.c create mode 100644 modules/pam_limits/.cvsignore create mode 100644 modules/pam_limits/Makefile create mode 100644 modules/pam_limits/README create mode 100755 modules/pam_limits/install_conf create mode 100644 modules/pam_limits/limits.skel create mode 100644 modules/pam_limits/pam_limits.c create mode 100644 modules/pam_listfile/.cvsignore create mode 100644 modules/pam_listfile/Makefile create mode 100644 modules/pam_listfile/README create mode 100644 modules/pam_listfile/pam_listfile.c create mode 100644 modules/pam_mail/.cvsignore create mode 100644 modules/pam_mail/Makefile create mode 100644 modules/pam_mail/README create mode 100644 modules/pam_mail/pam_mail.c create mode 100644 modules/pam_mkhomedir/.cvsignore create mode 100644 modules/pam_mkhomedir/Makefile create mode 100644 modules/pam_mkhomedir/pam_mkhomedir.c create mode 100644 modules/pam_motd/.cvsignore create mode 100644 modules/pam_motd/Makefile create mode 100644 modules/pam_motd/pam_motd.c create mode 100644 modules/pam_nologin/.cvsignore create mode 100644 modules/pam_nologin/Makefile create mode 100644 modules/pam_nologin/README create mode 100644 modules/pam_nologin/pam_nologin.c create mode 100644 modules/pam_permit/.cvsignore create mode 100644 modules/pam_permit/Makefile create mode 100644 modules/pam_permit/README create mode 100644 modules/pam_permit/pam_permit.c create mode 100644 modules/pam_pwdb/.cvsignore create mode 100644 modules/pam_pwdb/BUGS create mode 100644 modules/pam_pwdb/CHANGELOG create mode 100644 modules/pam_pwdb/Makefile create mode 100644 modules/pam_pwdb/README create mode 100644 modules/pam_pwdb/TODO create mode 100644 modules/pam_pwdb/bigcrypt.-c create mode 100644 modules/pam_pwdb/md5.c create mode 100644 modules/pam_pwdb/md5.h create mode 100644 modules/pam_pwdb/md5_crypt.c create mode 100644 modules/pam_pwdb/pam_pwdb.c create mode 100644 modules/pam_pwdb/pam_unix_acct.-c create mode 100644 modules/pam_pwdb/pam_unix_auth.-c create mode 100644 modules/pam_pwdb/pam_unix_md.-c create mode 100644 modules/pam_pwdb/pam_unix_passwd.-c create mode 100644 modules/pam_pwdb/pam_unix_pwupd.-c create mode 100644 modules/pam_pwdb/pam_unix_sess.-c create mode 100644 modules/pam_pwdb/pwdb_chkpwd.c create mode 100644 modules/pam_pwdb/support.-c create mode 100644 modules/pam_radius/.cvsignore create mode 100644 modules/pam_radius/Makefile create mode 100644 modules/pam_radius/README create mode 100644 modules/pam_radius/pam_radius.c create mode 100644 modules/pam_radius/pam_radius.h create mode 100644 modules/pam_rhosts/.cvsignore create mode 100644 modules/pam_rhosts/Makefile create mode 100644 modules/pam_rhosts/README create mode 100644 modules/pam_rhosts/pam_rhosts_auth.c create mode 100644 modules/pam_rootok/.cvsignore create mode 100644 modules/pam_rootok/Makefile create mode 100644 modules/pam_rootok/README create mode 100644 modules/pam_rootok/pam_rootok.c create mode 100644 modules/pam_securetty/.cvsignore create mode 100644 modules/pam_securetty/Makefile create mode 100644 modules/pam_securetty/README create mode 100644 modules/pam_securetty/pam_securetty.c create mode 100644 modules/pam_shells/.cvsignore create mode 100644 modules/pam_shells/Makefile create mode 100644 modules/pam_shells/README create mode 100644 modules/pam_shells/pam_shells.c create mode 100644 modules/pam_stress/.cvsignore create mode 100644 modules/pam_stress/Makefile create mode 100644 modules/pam_stress/README create mode 100644 modules/pam_stress/pam_stress.c create mode 100644 modules/pam_tally/.cvsignore create mode 100644 modules/pam_tally/Makefile create mode 100644 modules/pam_tally/README create mode 100644 modules/pam_tally/faillog.h create mode 100644 modules/pam_tally/pam_tally.c create mode 100644 modules/pam_tally/pam_tally_app.c create mode 100644 modules/pam_time/.cvsignore create mode 100644 modules/pam_time/Makefile create mode 100644 modules/pam_time/README create mode 100755 modules/pam_time/install_conf create mode 100644 modules/pam_time/pam_time.c create mode 100644 modules/pam_time/time.conf create mode 100644 modules/pam_unix/.cvsignore create mode 100644 modules/pam_unix/CHANGELOG create mode 100644 modules/pam_unix/Makefile create mode 100644 modules/pam_unix/README create mode 100644 modules/pam_unix/bigcrypt.c create mode 100644 modules/pam_unix/lckpwdf.-c create mode 100644 modules/pam_unix/md5.c create mode 100644 modules/pam_unix/md5.h create mode 100644 modules/pam_unix/md5_crypt.c create mode 100755 modules/pam_unix/need_nsl.sh create mode 100644 modules/pam_unix/pam_unix_acct.c create mode 100644 modules/pam_unix/pam_unix_auth.c create mode 100644 modules/pam_unix/pam_unix_passwd.c create mode 100644 modules/pam_unix/pam_unix_sess.c create mode 100644 modules/pam_unix/support.c create mode 100644 modules/pam_unix/support.h create mode 100644 modules/pam_unix/unix_chkpwd.c create mode 100644 modules/pam_unix/yppasswd.h create mode 100644 modules/pam_unix/yppasswd_xdr.c create mode 100644 modules/pam_userdb/.cvsignore create mode 100644 modules/pam_userdb/Makefile create mode 100644 modules/pam_userdb/README create mode 100644 modules/pam_userdb/conv.c create mode 100644 modules/pam_userdb/create.pl create mode 100755 modules/pam_userdb/libdbfound.sh create mode 100644 modules/pam_userdb/pam_userdb.c create mode 100644 modules/pam_userdb/pam_userdb.h create mode 100644 modules/pam_warn/.cvsignore create mode 100644 modules/pam_warn/Makefile create mode 100644 modules/pam_warn/README create mode 100644 modules/pam_warn/pam_warn.c create mode 100644 modules/pam_wheel/.cvsignore create mode 100644 modules/pam_wheel/Makefile create mode 100644 modules/pam_wheel/README create mode 100644 modules/pam_wheel/pam_wheel.c create mode 100755 modules/register_static (limited to 'modules') diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 00000000..9b89ccfd --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,70 @@ +# $Id$ +# +# Makefile +# +# This makefile controls the build process of shared and static PAM modules. +# +# + +MODDIRS=$(shell /bin/ls -d pam_*) + +# //////////////////////////////////////////////////// +# // You should not modify anything below this line // +# //////////////////////////////////////////////////// + +dummy: + @echo "*** This is not a top-level Makefile! ***" + +# ----------------------------------------------------------- + +all: + @echo modules for $(OS) are: + @ls -d $(MODDIRS) 2>/dev/null ; echo :-------- + @echo +ifdef STATIC + rm -f ./_static_module_* +endif + @for i in $(MODDIRS) ; do \ + if [ -d $$i ]; then { \ + $(MAKE) -C $$i all ; \ + if [ $$? -ne 0 ]; then exit 1 ; fi ; \ + } elif [ -f ./.$$i ]; then { \ + cat ./.$$i ; \ + } fi ; \ + done + +download: + @./download-all + +install: + for i in $(MODDIRS) ; do \ + if [ -d $$i ]; then { \ + $(MAKE) -C $$i install ; \ + if [ $$? -ne 0 ]; then exit 1 ; fi ; \ + } fi ; \ + done + +remove: + for i in $(MODDIRS) ; do \ + if [ -d $$i ]; then { \ + $(MAKE) -C $$i remove ; \ + } fi ; \ + done + +lclean: + rm -f _static_module_* + +clean: lclean + for i in $(MODDIRS) ; do \ + if [ -d $$i ]; then { \ + $(MAKE) -C $$i clean ; \ + } fi ; \ + done + +extraclean: lclean + for i in $(MODDIRS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i extraclean ; \ + fi ; \ + done + diff --git a/modules/README b/modules/README new file mode 100644 index 00000000..86415947 --- /dev/null +++ b/modules/README @@ -0,0 +1,55 @@ +This directory contains the modules. + +If you want to reserve a module name please email +and announce its name. Andrew Morgan, , will +add it to the Makefile in the next release of Linux-PAM. + +As of Linux-PAM-0.40 modules can optionally conform to the static +modules conventions. + +This file was updated for Linux-PAM-0.53. + +The conventions are as follows: + +There are only 6 functions that a module may declare as "public" they +fall into 4 managment groups as follows: + + functions Management group + ------------------------------------------ ---------------- + pam_sm_authenticate, pam_sm_setcred, PAM_SM_AUTH + pam_sm_acct_mgmt, PAM_SM_ACCOUNT + pam_sm_open_session, pam_sm_close_session, PAM_SM_SESSION + pam_sm_chauthtok PAM_SM_PASSWORD + +If a module contains definitions for any of the above functions, it +must supply definitions for all of the functions in the corresponding +management group. + +The header file that defines the ANSI prototypes for these functions +is . In the case that the module wishes to +offer the functions of a given managment group, it must #define +PAM_SM_XXX, where XXX is one of the above four tokens. These +definitions must occur *prior* to the +#include line. + +The pam_sm_... functions should be defined to be of type 'PAM_EXTERN int'. + +In the case that a module is being compiled with PAM_STATIC #define'd +it should also define a globally accessible structure +_"NAME"_modstruct containing references to each of the functions +defined by the module. (this structure is defined in +. "NAME" is the title of the module +(eg. "pam_deny") + +If a module wants to be included in the static libpam.a its Makefile +should execute "register_static" with appropriate arguments (in this +directory). + +[ +For SIMPLE working examples, see + + ./modules/pam_deny/* and ./modules/pam_rootok/* +.] + +Andrew Morgan +96/11/10 diff --git a/modules/dont_makefile b/modules/dont_makefile new file mode 100644 index 00000000..f256ce1b --- /dev/null +++ b/modules/dont_makefile @@ -0,0 +1,19 @@ +######################################################################### +# This is a makefile that does nothing. It is designed to be included +# by module Makefile-s when they are not compatable with the local +# system +######################################################################### + +all: + @echo "This module will not be compiled on this system" + +extraclean: clean + +install: clean + +clean: + @echo "Nothing to do" + +######################################################################### +# all over.. +######################################################################### diff --git a/modules/download-all b/modules/download-all new file mode 100755 index 00000000..427d0bba --- /dev/null +++ b/modules/download-all @@ -0,0 +1,30 @@ +#!/bin/sh +# +# $Id$ +# +cat < 1997/06/15 +############################################################################ diff --git a/modules/pam_access/access.conf b/modules/pam_access/access.conf new file mode 100644 index 00000000..abfefa5e --- /dev/null +++ b/modules/pam_access/access.conf @@ -0,0 +1,52 @@ +# Login access control table. +# +# When someone logs in, the table is scanned for the first entry that +# matches the (user, host) combination, or, in case of non-networked +# logins, the first entry that matches the (user, tty) combination. The +# permissions field of that table entry determines whether the login will +# be accepted or refused. +# +# Format of the login access control table is three fields separated by a +# ":" character: +# +# permission : users : origins +# +# The first field should be a "+" (access granted) or "-" (access denied) +# character. +# +# The second field should be a list of one or more login names, group +# names, or ALL (always matches). A pattern of the form user@host is +# matched when the login name matches the "user" part, and when the +# "host" part matches the local machine name. +# +# The third field should be a list of one or more tty names (for +# non-networked logins), host names, domain names (begin with "."), host +# addresses, internet network numbers (end with "."), ALL (always +# matches) or LOCAL (matches any string that does not contain a "." +# character). +# +# If you run NIS you can use @netgroupname in host or user patterns; this +# even works for @usergroup@@hostgroup patterns. Weird. +# +# The EXCEPT operator makes it possible to write very compact rules. +# +# The group file is searched only when a name does not match that of the +# logged-in user. Both the user's primary group is matched, as well as +# groups in which users are explicitly listed. +# +############################################################################## +# +# Disallow console logins to all but a few accounts. +# +#-:ALL EXCEPT wheel shutdown sync:console +# +# Disallow non-local logins to privileged accounts (group wheel). +# +#-:wheel:ALL EXCEPT LOCAL .win.tue.nl +# +# Some accounts are not allowed to login from anywhere: +# +#-:wsbscaro wsbsecr wsbspac wsbsym wscosor wstaiwde:ALL +# +# All other accounts are allowed to login from anywhere. +# diff --git a/modules/pam_access/install_conf b/modules/pam_access/install_conf new file mode 100755 index 00000000..0667b5ec --- /dev/null +++ b/modules/pam_access/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +CONF=./access.conf +QUIET_INSTALL=../../.quiet_install +MODULE=pam_access + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_access/pam_access.c b/modules/pam_access/pam_access.c new file mode 100644 index 00000000..12133392 --- /dev/null +++ b/modules/pam_access/pam_access.c @@ -0,0 +1,424 @@ +/* pam_access module */ + +/* + * Written by Alexei Nogin 1997/06/15 + * (I took login_access from logdaemon-5.6 and converted it to PAM + * using parts of pam_time code.) + * + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +/* man page says above file includes this... */ +extern int gethostname(char *name, size_t len); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BROKEN_NETWORK_MATCH +# include +# include +#endif + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_ACCOUNT + +#include +#include + +/* --- static functions for checking whether the user should be let in --- */ + +static void _log_err(const char *format, ... ) +{ + va_list args; + + va_start(args, format); + openlog("pam_access", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(LOG_ERR, format, args); + va_end(args); + closelog(); +} + +#define PAM_ACCESS_CONFIG CONFILE + +int strcasecmp(const char *s1, const char *s2); + +/* login_access.c from logdaemon-5.6 with several changes by A.Nogin: */ + + /* + * This module implements a simple but effective form of login access + * control based on login names and on host (or domain) names, internet + * addresses (or network numbers), or on terminal line names in case of + * non-networked logins. Diagnostics are reported through syslog(3). + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ + +#if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64) +#undef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + + /* Delimiters for fields and for lists of users, ttys or hosts. */ + +static char fs[] = ":"; /* field separator */ +static char sep[] = ", \t"; /* list-element separator */ + + /* Constants to be used in assignments only, not in comparisons... */ + +#define YES 1 +#define NO 0 + + /* + * A structure to bundle up all login-related information to keep the + * functional interfaces as generic as possible. + */ +struct login_info { + struct passwd *user; + char *from; +}; + +typedef int match_func (char *, struct login_info *); + +static int list_match (char *, struct login_info *, + match_func *); +static int user_match (char *, struct login_info *); +static int from_match (char *, struct login_info *); +static int string_match (char *, char *); + +/* login_access - match username/group and host/tty with access control file */ + +static int login_access(struct passwd *user, char *from) +{ + struct login_info item; + FILE *fp; + char line[BUFSIZ]; + char *perm; /* becomes permission field */ + char *users; /* becomes list of login names */ + char *froms; /* becomes list of terminals or hosts */ + int match = NO; + int end; + int lineno = 0; /* for diagnostics */ + + /* + * Bundle up the arguments to avoid unnecessary clumsiness lateron. + */ + item.user = user; + item.from = from; + + /* + * Process the table one line at a time and stop at the first match. + * Blank lines and lines that begin with a '#' character are ignored. + * Non-comment lines are broken at the ':' character. All fields are + * mandatory. The first field should be a "+" or "-" character. A + * non-existing table means no access control. + */ + + if ((fp = fopen(PAM_ACCESS_CONFIG, "r"))!=NULL) { + while (!match && fgets(line, sizeof(line), fp)) { + lineno++; + if (line[end = strlen(line) - 1] != '\n') { + _log_err("%s: line %d: missing newline or line too long", + PAM_ACCESS_CONFIG, lineno); + continue; + } + if (line[0] == '#') + continue; /* comment line */ + while (end > 0 && isspace(line[end - 1])) + end--; + line[end] = 0; /* strip trailing whitespace */ + if (line[0] == 0) /* skip blank lines */ + continue; + if (!(perm = strtok(line, fs)) + || !(users = strtok((char *) 0, fs)) + || !(froms = strtok((char *) 0, fs)) + || strtok((char *) 0, fs)) { + _log_err("%s: line %d: bad field count", PAM_ACCESS_CONFIG, lineno); + continue; + } + if (perm[0] != '+' && perm[0] != '-') { + _log_err("%s: line %d: bad first field", PAM_ACCESS_CONFIG, lineno); + continue; + } + match = (list_match(froms, &item, from_match) + && list_match(users, &item, user_match)); + } + (void) fclose(fp); + } else if (errno != ENOENT) { + _log_err("cannot open %s: %m", PAM_ACCESS_CONFIG); + } + return (match == 0 || (line[0] == '+')); +} + +/* list_match - match an item against a list of tokens with exceptions */ + +static int list_match(char *list, struct login_info *item, match_func *match_fn) +{ + char *tok; + int match = NO; + + /* + * Process tokens one at a time. We have exhausted all possible matches + * when we reach an "EXCEPT" token or the end of the list. If we do find + * a match, look for an "EXCEPT" list and recurse to determine whether + * the match is affected by any exceptions. + */ + + for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { + if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */ + break; + if ((match = (*match_fn) (tok, item))) /* YES */ + break; + } + /* Process exceptions to matches. */ + + if (match != NO) { + while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT")) + /* VOID */ ; + if (tok == 0 || list_match((char *) 0, item, match_fn) == NO) + return (match); + } + return (NO); +} + +/* myhostname - figure out local machine name */ + +static char * myhostname(void) +{ + static char name[MAXHOSTNAMELEN + 1]; + + gethostname(name, MAXHOSTNAMELEN); + name[MAXHOSTNAMELEN] = 0; + return (name); +} + +/* netgroup_match - match group against machine or user */ + +static int netgroup_match(char *group, char *machine, char *user) +{ +#ifdef NIS + static char *mydomain = 0; + + if (mydomain == 0) + yp_get_default_domain(&mydomain); + return (innetgr(group, machine, user, mydomain)); +#else + _log_err("NIS netgroup support not configured"); + return (NO); +#endif +} + +/* user_match - match a username against one token */ + +static int user_match(char *tok, struct login_info *item) +{ + char *string = item->user->pw_name; + struct login_info fake_item; + struct group *group; + int i; + char *at; + + /* + * If a token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the username, if the + * token is a group that contains the username, or if the token is the + * name of the user's primary group. + */ + + if ((at = strchr(tok + 1, '@')) != 0) { /* split user@host pattern */ + *at = 0; + fake_item.from = myhostname(); + return (user_match(tok, item) && from_match(at + 1, &fake_item)); + } else if (tok[0] == '@') { /* netgroup */ + return (netgroup_match(tok + 1, (char *) 0, string)); + } else if (string_match(tok, string)) { /* ALL or exact match */ + return (YES); + } else if ((group = getgrnam(tok))) { /* try group membership */ + if (item->user->pw_gid == group->gr_gid) + return (YES); + for (i = 0; group->gr_mem[i]; i++) + if (strcasecmp(string, group->gr_mem[i]) == 0) + return (YES); + } + return (NO); +} + +/* from_match - match a host or tty against a list of tokens */ + +static int from_match(char *tok, struct login_info *item) +{ + char *string = item->from; + int tok_len; + int str_len; + + /* + * If a token has the magic value "ALL" the match always succeeds. Return + * YES if the token fully matches the string. If the token is a domain + * name, return YES if it matches the last fields of the string. If the + * token has the magic value "LOCAL", return YES if the string does not + * contain a "." character. If the token is a network number, return YES + * if it matches the head of the string. + */ + + if (tok[0] == '@') { /* netgroup */ + return (netgroup_match(tok + 1, string, (char *) 0)); + } else if (string_match(tok, string)) { /* ALL or exact match */ + return (YES); + } else if (tok[0] == '.') { /* domain: match last fields */ + if ((str_len = strlen(string)) > (tok_len = strlen(tok)) + && strcasecmp(tok, string + str_len - tok_len) == 0) + return (YES); + } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */ + if (strchr(string, '.') == 0) + return (YES); +#ifdef BROKEN_NETWORK_MATCH + } else if (tok[(tok_len = strlen(tok)) - 1] == '.' /* network */ + && strncmp(tok, string, tok_len) == 0) { + return (YES); +#else /* BROKEN_NETWORK_MATCH */ + } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { + /* + The code below does a more correct check if the address specified + by "string" starts from "tok". + 1998/01/27 Andrey V. Savochkin + */ + struct hostent *h; + char hn[3+1+3+1+3+1+3+1]; + int r; + + h = gethostbyname(string); + if (h == NULL) + return (NO); + if (h->h_addrtype != AF_INET) + return (NO); + if (h->h_length != 4) + return (NO); /* only IPv4 addresses (SAW) */ + r = snprintf(hn, sizeof(hn), "%u.%u.%u.%u", + (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], + (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + if (r < 0 || r >= sizeof(hn)) + return (NO); + if (!strncmp(tok, hn, tok_len)) + return (YES); +#endif /* BROKEN_NETWORK_MATCH */ + } + return (NO); +} + +/* string_match - match a string against one token */ + +static int string_match(char *tok, char *string) +{ + + /* + * If the token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the string. + */ + + if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ + return (YES); + } else if (strcasecmp(tok, string) == 0) { /* try exact match */ + return (YES); + } + return (NO); +} + +/* end of login_access.c */ + +int strcasecmp(const char *s1, const char *s2) +{ + while ((toupper(*s1)==toupper(*s2)) && (*s1) && (*s2)) {s1++; s2++;} + return(toupper(*s1)-toupper(*s2)); +} + +/* --- public account management functions --- */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + const char *user=NULL; + char *from=NULL; + struct passwd *user_pw; + + /* set username */ + + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL + || *user == '\0') { + _log_err("cannot determine the user's name"); + return PAM_USER_UNKNOWN; + } + + /* remote host name */ + + if (pam_get_item(pamh, PAM_RHOST, (const void **)&from) + != PAM_SUCCESS) { + _log_err("cannot find the remote host name"); + return PAM_ABORT; + } + + if (from==NULL) { + + /* local login, set tty name */ + + if (pam_get_item(pamh, PAM_TTY, (const void **)&from) != PAM_SUCCESS + || from == NULL) { + D(("PAM_TTY not set, probing stdin")); + from = ttyname(STDIN_FILENO); + if (from == NULL) { + _log_err("couldn't get the tty name"); + return PAM_ABORT; + } + if (pam_set_item(pamh, PAM_TTY, from) != PAM_SUCCESS) { + _log_err("couldn't set tty name"); + return PAM_ABORT; + } + } + if (strncmp("/dev/",from,5) == 0) { /* strip leading /dev/ */ + from += 5; + } + + } + if ((user_pw=getpwnam(user))==NULL) return (PAM_USER_UNKNOWN); + if (login_access(user_pw,from)) return (PAM_SUCCESS); else { + _log_err("access denied for user `%s' from `%s'",user,from); + return (PAM_PERM_DENIED); + } +} + +/* end of module definition */ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_access_modstruct = { + "pam_access", + NULL, + NULL, + pam_sm_acct_mgmt, + NULL, + NULL, + NULL +}; +#endif + diff --git a/modules/pam_cracklib/.cvsignore b/modules/pam_cracklib/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_cracklib/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_cracklib/Makefile b/modules/pam_cracklib/Makefile new file mode 100644 index 00000000..554362d4 --- /dev/null +++ b/modules/pam_cracklib/Makefile @@ -0,0 +1,118 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Cristian Gafton 1996/09/10 +# + +ifndef FULL_LINUX_PAM_SOURCE_TREE +# +# here you should make default variable defines... +# +MKDIR=mkdir -p +LD_D=gcc -shared -Xlinker -x +INSTALL=install +SECUREDIR=/usr/lib/security +# +HAVE_CRACKLIB=yes +EXTRALS=-lcrypt +endif + +ifeq ($(HAVE_CRACKLIB),yes) + +TITLE=pam_cracklib +CRACKLIB=-lcrack +ifeq ($(shell if [ -f /usr/lib/cracklib_dict.hwm ]; then echo yes ; fi),yes) + CRACKLIB_DICTPATH=/usr/lib/cracklib_dict +else + CRACKLIB_DICTPATH=/usr/share/dict/cracklib_dict +endif +# +ifeq ($(shell if [ -f /usr/include/crypt.h ]; then echo yes ; fi),yes) + NEED_CRYPT_HEADER=-DNEED_CRYPT_HEADER +endif +# +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +ifdef CRACKLIB_DICTPATH +CFLAGS+=-DCRACKLIB_DICTPATH=\"$(CRACKLIB_DICTPATH)\" +endif + +dynamic/%.o : %.c + $(CC) $(NEED_CRYPT_HEADER) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(NEED_CRYPT_HEADER) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) Makefile + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(CRACKLIB) $(EXTRALS) $(LINKLIBS) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ *.so + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + +else + +include ../dont_makefile + +endif diff --git a/modules/pam_cracklib/README b/modules/pam_cracklib/README new file mode 100644 index 00000000..e4b02731 --- /dev/null +++ b/modules/pam_cracklib/README @@ -0,0 +1,21 @@ + +pam_cracklib: + check the passwd against dictionary words. + +RECOGNIZED ARGUMENTS: + debug verbose log + + type=XXX alter the message printed as a prompt to the user. + the message printed is in the form + "New XXX password: ". + Default XXX=UNIX + + retry=N Prompt user at most N times before returning with + error. Default N=1. + +MODULE SERVICES PROVIDED: + passwd chauthtok + +AUTHOR: + Cristian Gafton + diff --git a/modules/pam_cracklib/pam_cracklib.c b/modules/pam_cracklib/pam_cracklib.c new file mode 100644 index 00000000..d3d74947 --- /dev/null +++ b/modules/pam_cracklib/pam_cracklib.c @@ -0,0 +1,776 @@ +/* pam_cracklib module */ + +/* + * 0.85. added six new options to use this with long passwords. + * 0.8. tidied output and improved D(()) usage for debugging. + * 0.7. added support for more obscure checks for new passwd. + * 0.6. root can reset user passwd to any values (it's only warned) + * 0.5. supports retries - 'retry=N' argument + * 0.4. added argument 'type=XXX' for 'New XXX password' prompt + * 0.3. Added argument 'debug' + * 0.2. new password is feeded to cracklib for verify after typed once + * 0.1. First release + */ + +/* + * Written by Cristian Gafton 1996/09/10 + * Long password support by Philip W. Dalrymple 1997/07/18 + * See the end of the file for Copyright Information + * + * Modification for long password systems (>8 chars). The original + * module had problems when used in a md5 password system in that it + * allowed too short passwords but required that at least half of the + * bytes in the new password did not appear in the old one. this + * action is still the default and the changes should not break any + * current user. This modification adds 6 new options, one to set the + * number of bytes in the new password that are not in the old one, + * the other five to control the length checking, these are all + * documented (or will be before anyone else sees this code) in the PAM + * S.A.G. in the section on the cracklib module. + */ + +#define _GNU_SOURCE +#define _BSD_SOURCE + +#include +#ifdef NEED_CRYPT_HEADER +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +extern char *FascistCheck(char *pw, const char *dictpath); + +#ifndef CRACKLIB_DICTPATH +#define CRACKLIB_DICTPATH "/usr/share/dict/cracklib_dict" +#endif + +#define PROMPT1 "New %s password: " +#define PROMPT2 "Retype new %s password: " +#define MISTYPED_PASS "Sorry, passwords do not match" + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_PASSWORD + +#include +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-Cracklib", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ +#define PAM_DEBUG_ARG 0x0001 + +/* module data - AGM: */ + +struct cracklib_options { + int retry_times; + int diff_ok; + int min_length; + int dig_credit; + int up_credit; + int low_credit; + int oth_credit; + int use_authtok; + char prompt_type[BUFSIZ]; +}; + +static int _pam_parse(struct cracklib_options *opt, int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + char *ep = NULL; + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strncmp(*argv,"type=",5)) + strcpy(opt->prompt_type, *argv+5); + else if (!strncmp(*argv,"retry=",6)) { + opt->retry_times = strtol(*argv+6,&ep,10); + if (!ep || (opt->retry_times < 1)) + opt->retry_times = 1; + } else if (!strncmp(*argv,"difok=",6)) { + opt->diff_ok = strtol(*argv+6,&ep,10); + if (!ep || (opt->diff_ok < 0)) + opt->diff_ok = 10; + } else if (!strncmp(*argv,"minlen=",7)) { + opt->min_length = strtol(*argv+7,&ep,10); + if (!ep || (opt->min_length < 5)) + opt->min_length = 5; + } else if (!strncmp(*argv,"dcredit=",8)) { + opt->dig_credit = strtol(*argv+8,&ep,10); + if (!ep || (opt->dig_credit < 0)) + opt->dig_credit = 0; + } else if (!strncmp(*argv,"ucredit=",8)) { + opt->up_credit = strtol(*argv+8,&ep,10); + if (!ep || (opt->up_credit < 0)) + opt->up_credit = 0; + } else if (!strncmp(*argv,"lcredit=",8)) { + opt->low_credit = strtol(*argv+8,&ep,10); + if (!ep || (opt->low_credit < 0)) + opt->low_credit = 0; + } else if (!strncmp(*argv,"ocredit=",8)) { + opt->oth_credit = strtol(*argv+8,&ep,10); + if (!ep || (opt->oth_credit < 0)) + opt->oth_credit = 0; + } else if (!strncmp(*argv,"use_authtok",11)) { + opt->use_authtok = 1; + } else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* Helper functions */ + +/* this is a front-end for module-application conversations */ +static int converse(pam_handle_t *pamh, int ctrl, int nargs, + struct pam_message **message, + struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + + if ( retval == PAM_SUCCESS ) { + retval = conv->conv(nargs, (const struct pam_message **)message, + response, conv->appdata_ptr); + if (retval != PAM_SUCCESS && (ctrl && PAM_DEBUG_ARG)) { + _pam_log(LOG_DEBUG, "conversation failure [%s]", + pam_strerror(pamh, retval)); + } + } else { + _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]", + pam_strerror(pamh, retval)); + } + + return retval; /* propagate error status */ +} + +static int make_remark(pam_handle_t *pamh, unsigned int ctrl, + int type, const char *text) +{ + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + int retval; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + resp = NULL; + + retval = converse(pamh, ctrl, 1, pmsg, &resp); + if (retval == PAM_SUCCESS) + _pam_drop_reply(resp, 1); + + return retval; +} + +/* use this to free strings. ESPECIALLY password strings */ +static char *_pam_delete(register char *xx) +{ + _pam_overwrite(xx); + free(xx); + return NULL; +} + +/* + * can't be a palindrome - like `R A D A R' or `M A D A M' + */ +static int palindrome(const char *old, const char *new) +{ + int i, j; + + i = strlen (new); + + for (j = 0;j < i;j++) + if (new[i - j - 1] != new[j]) + return 0; + + return 1; +} + +/* + * more than half of the characters are different ones. + * or at least diff_ok are different + * NOTE that the defaults are NOT the same as befor this + * change. as long as there are at least 10 different bytes + * in a new password it will now pass even if the password + * is longer than 20 bytes (MD5) + */ + +static int similiar(struct cracklib_options *opt, const char *old, const char *new) +{ + int i, j; + + for (i = j = 0;new[i] && old[i];i++) + if (strchr (new, old[i])) + j++; + + if (j >= opt->diff_ok || i >= j * 2) + return 0; + + return 1; +} + +/* + * a nice mix of characters. + */ +static int simple(struct cracklib_options *opt, const char *old, const char *new) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int size; + int i; + + for (i = 0;new[i];i++) { + if (isdigit (new[i])) + digits++; + else if (isupper (new[i])) + uppers++; + else if (islower (new[i])) + lowers++; + else + others++; + } + + /* + * The scam was this - a password of only one character type + * must be 8 letters long. Two types, 7, and so on. + * This is now changed, the base size and the credits or defaults + * see the docs on the module for info on these parameters, the + * defaults cause the effect to be the same as before the change + */ + + if (digits > opt->dig_credit) + digits = opt->dig_credit; + + if (uppers > opt->up_credit) + uppers = opt->up_credit; + + if (lowers > opt->low_credit) + lowers = opt->low_credit; + + if (others > opt->oth_credit) + others = opt->oth_credit; + + size = opt->min_length; + size -= digits; + size -= uppers; + size -= lowers; + size -= others; + + if (size <= i) + return 0; + + return 1; +} + +static char * str_lower(char *string) +{ + char *cp; + + for (cp = string; *cp; cp++) + *cp = tolower(*cp); + return string; +} + +static const char * password_check(struct cracklib_options *opt, const char *old, const char *new) +{ + const char *msg = NULL; + char *oldmono, *newmono, *wrapped; + + if (strcmp(new, old) == 0) { + msg = "is the same as the old one"; + return msg; + } + + newmono = str_lower(x_strdup(new)); + oldmono = str_lower(x_strdup(old)); + wrapped = malloc(strlen(oldmono) * 2 + 1); + strcpy (wrapped, oldmono); + strcat (wrapped, oldmono); + + if (palindrome(oldmono, newmono)) + msg = "is a palindrome"; + + if (!msg && strcmp(oldmono, newmono) == 0) + msg = "case changes only"; + + if (!msg && similiar(opt, oldmono, newmono)) + msg = "is too similiar to the old one"; + + if (!msg && simple(opt, old, new)) + msg = "is too simple"; + + if (!msg && strstr(wrapped, newmono)) + msg = "is rotated"; + + memset(newmono, 0, strlen(newmono)); + memset(oldmono, 0, strlen(oldmono)); + memset(wrapped, 0, strlen(wrapped)); + free(newmono); + free(oldmono); + free(wrapped); + + return msg; +} + + +#define OLD_PASSWORDS_FILE "/etc/security/opasswd" + +static const char * check_old_password(const char *forwho, const char *newpass) +{ + static char buf[16384]; + char *s_luser, *s_uid, *s_npas, *s_pas; + const char *msg = NULL; + FILE *opwfile; + + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (opwfile == NULL) + return NULL; + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, strlen(forwho))) { + buf[strlen(buf)-1] = '\0'; + s_luser = strtok(buf, ":,"); + s_uid = strtok(NULL, ":,"); + s_npas = strtok(NULL, ":,"); + s_pas = strtok(NULL, ":,"); + while (s_pas != NULL) { + if (!strcmp(crypt(newpass, s_pas), s_pas)) { + msg = "has been already used"; + break; + } + s_pas = strtok(NULL, ":,"); + } + break; + } + } + fclose(opwfile); + + return msg; +} + + +static int _pam_unix_approve_pass(pam_handle_t *pamh, + unsigned int ctrl, + struct cracklib_options *opt, + const char *pass_old, + const char *pass_new) +{ + const char *msg = NULL; + const char *user; + int retval; + + if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) { + if (ctrl && PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "bad authentication token"); + make_remark(pamh, ctrl, PAM_ERROR_MSG, + pass_new == NULL ? + "No password supplied":"Password unchanged" ); + return PAM_AUTHTOK_ERR; + } + + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place + */ + msg = password_check(opt, pass_old,pass_new); + if (!msg) { + retval = pam_get_item(pamh, PAM_USER, (const void **)&user); + if (retval != PAM_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_ERR,"Can not get username"); + return PAM_AUTHTOK_ERR; + } + } + msg = check_old_password(user, pass_new); + } + + if (msg) { + char remark[BUFSIZ]; + + memset(remark,0,sizeof(remark)); + sprintf(remark,"BAD PASSWORD: %s",msg); + if (ctrl && PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE, "new passwd fails strength check: %s", + msg); + make_remark(pamh, ctrl, PAM_ERROR_MSG, remark); + return PAM_AUTHTOK_ERR; + }; + return PAM_SUCCESS; + +} + +/* The Main Thing (by Cristian Gafton, CEO at this module :-) + * (stolen from http://home.netscape.com) + */ +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + struct cracklib_options options; + + options.retry_times = 1; + options.diff_ok = 10; + options.min_length = 9; + options.dig_credit = 1; + options.up_credit = 1; + options.low_credit = 1; + options.oth_credit = 1; + options.use_authtok = 0; + memset(options.prompt_type, 0, BUFSIZ); + + ctrl = _pam_parse(&options, argc, argv); + + D(("called.")); + if (!options.prompt_type[0]) + strcpy(options.prompt_type,"UNIX"); + + if (flags & PAM_PRELIM_CHECK) { + /* Check for passwd dictionary */ + struct stat st; + char buf[sizeof(CRACKLIB_DICTPATH)+10]; + + D(("prelim check")); + + memset(buf,0,sizeof(buf)); /* zero the buffer */ + sprintf(buf,"%s.pwd",CRACKLIB_DICTPATH); + + if (!stat(buf,&st) && st.st_size) + return PAM_SUCCESS; + else { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"dict path '%s'[.pwd] is invalid", + CRACKLIB_DICTPATH); + return PAM_ABORT; + } + + /* Not reached */ + return PAM_SERVICE_ERR; + + } else if (flags & PAM_UPDATE_AUTHTOK) { + int retval; + char *token1, *token2, *oldtoken; + struct pam_message msg[1],*pmsg[1]; + struct pam_response *resp; + const char *cracklib_dictpath = CRACKLIB_DICTPATH; + char prompt[BUFSIZ]; + + D(("do update")); + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **)&oldtoken); + if (retval != PAM_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_ERR,"Can not get old passwd"); + oldtoken=NULL; + retval = PAM_SUCCESS; + } + + do { + /* + * make sure nothing inappropriate gets returned + */ + token1 = token2 = NULL; + + if (!options.retry_times) { + D(("returning %s because maxtries reached", + pam_strerror(pamh, retval))); + return retval; + } + + /* Planned modus operandi: + * Get a passwd. + * Verify it against cracklib. + * If okay get it a second time. + * Check to be the same with the first one. + * set PAM_AUTHTOK and return + */ + + if (options.use_authtok == 1) { + const char *item = NULL; + + retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _pam_log(LOG_ALERT + ,"pam_get_item returned error to pam_cracklib" + ); + } else if (item != NULL) { /* we have a password! */ + token1 = x_strdup(item); + item = NULL; + } else { + retval = PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } + + } else { + /* Prepare to ask the user for the first time */ + memset(prompt,0,sizeof(prompt)); + sprintf(prompt,PROMPT1,options.prompt_type); + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + msg[0].msg = prompt; + + resp = NULL; + retval = converse(pamh, ctrl, 1, pmsg, &resp); + if (resp != NULL) { + /* interpret the response */ + if (retval == PAM_SUCCESS) { /* a good conversation */ + token1 = x_strdup(resp[0].resp); + if (token1 == NULL) { + _pam_log(LOG_NOTICE, + "could not recover authentication token 1"); + retval = PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * tidy up the conversation (resp_retcode) is ignored + */ + _pam_drop_reply(resp, 1); + } else { + retval = (retval == PAM_SUCCESS) ? + PAM_AUTHTOK_RECOVER_ERR:retval ; + } + } + + if (retval != PAM_SUCCESS) { + if (ctrl && PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG,"unable to obtain a password"); + continue; + } + + D(("testing password, retval = %s", pam_strerror(pamh, retval))); + /* now test this passwd against cracklib */ + { + char *crack_msg; + char remark[BUFSIZ]; + + bzero(remark,sizeof(remark)); + D(("against cracklib")); + if ((crack_msg = FascistCheck(token1, cracklib_dictpath))) { + if (ctrl && PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG,"bad password: %s",crack_msg); + sprintf(remark,"BAD PASSWORD: %s", crack_msg); + make_remark(pamh, ctrl, PAM_ERROR_MSG, remark); + if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) + retval = PAM_AUTHTOK_ERR; + else + retval = PAM_SUCCESS; + } else { + /* check it for strength too... */ + D(("for strength")); + if (oldtoken) { + retval = _pam_unix_approve_pass(pamh,ctrl,&options, + oldtoken,token1); + if (retval != PAM_SUCCESS) { + if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) + retval = PAM_AUTHTOK_ERR; + else + retval = PAM_SUCCESS; + } + } + } + } + + D(("after testing: retval = %s", pam_strerror(pamh, retval))); + /* if cracklib/strength check said it is a bad passwd... */ + if ((retval != PAM_SUCCESS) && (retval != PAM_IGNORE)) { + int temp_unused; + + temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL); + token1 = _pam_delete(token1); + continue; + } + + /* Now we have a good passwd. Ask for it once again */ + + if (options.use_authtok == 0) { + bzero(prompt,sizeof(prompt)); + sprintf(prompt,PROMPT2,options.prompt_type); + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + msg[0].msg = prompt; + + resp = NULL; + retval = converse(pamh, ctrl, 1, pmsg, &resp); + if (resp != NULL) { + /* interpret the response */ + if (retval == PAM_SUCCESS) { /* a good conversation */ + token2 = x_strdup(resp[0].resp); + if (token2 == NULL) { + _pam_log(LOG_NOTICE, + "could not recover authentication token 2"); + retval = PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * tidy up the conversation (resp_retcode) is ignored + */ + _pam_drop_reply(resp, 1); + } else { + retval = (retval == PAM_SUCCESS) ? + PAM_AUTHTOK_RECOVER_ERR:retval ; + } + + if (retval != PAM_SUCCESS) { + if (ctrl && PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG + ,"unable to obtain the password a second time"); + continue; + } + + /* Hopefully now token1 and token2 the same password ... */ + if (strcmp(token1,token2) != 0) { + /* tell the user */ + make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS); + token1 = _pam_delete(token1); + token2 = _pam_delete(token2); + pam_set_item(pamh, PAM_AUTHTOK, NULL); + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"Password mistyped"); + retval = PAM_AUTHTOK_RECOVER_ERR; + continue; + } + + /* Yes, the password was typed correct twice + * we store this password as an item + */ + + { + const char *item = NULL; + + retval = pam_set_item(pamh, PAM_AUTHTOK, token1); + + /* clean up */ + token1 = _pam_delete(token1); + token2 = _pam_delete(token2); + + if ( (retval != PAM_SUCCESS) || + ((retval = pam_get_item(pamh, PAM_AUTHTOK, + (const void **)&item) + ) != PAM_SUCCESS) ) { + _pam_log(LOG_CRIT, "error manipulating password"); + continue; + } + item = NULL; /* break link to password */ + return PAM_SUCCESS; + } + } + + } while (options.retry_times--); + + } else { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE, "UNKNOWN flags setting %02X",flags); + return PAM_SERVICE_ERR; + } + + /* Not reached */ + return PAM_SERVICE_ERR; +} + + + +#ifdef PAM_STATIC +/* static module data */ +struct pam_module _pam_cracklib_modstruct = { + "pam_cracklib", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok +}; +#endif + +/* + * Copyright (c) Cristian Gafton , 1996. + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The following copyright was appended for the long password support + * added with the libpam 0.58 release: + * + * Modificaton Copyright (c) Philip W. Dalrymple III + * 1997. All rights reserved + * + * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_deny/.cvsignore b/modules/pam_deny/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_deny/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_deny/Makefile b/modules/pam_deny/Makefile new file mode 100644 index 00000000..e6fa2e60 --- /dev/null +++ b/modules/pam_deny/Makefile @@ -0,0 +1,131 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:33 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.7 1997/04/05 06:43:41 morgan +# full-source-tree and fakeroot +# +# Revision 1.6 1997/02/15 19:04:27 morgan +# fixed email +# +# Revision 1.5 1996/11/10 20:11:48 morgan +# crossplatform support +# +# Revision 1.4 1996/09/05 06:50:12 morgan +# ld --> gcc +# +# Revision 1.3 1996/05/26 15:48:38 morgan +# make dynamic and static dirs +# +# Revision 1.2 1996/05/26 04:00:16 morgan +# changes for automated static/dynamic modules +# +# Revision 1.1 1996/03/16 17:47:36 morgan +# Initial revision +# +# +# Created by Andrew Morgan 1996/3/11 +# + +# Convenient defaults for compiling independently of the full source +# tree. +ifndef FULL_LINUX_PAM_SOURCE_TREE +export DYNAMIC=-DPAM_DYNAMIC +export CC=gcc +export CFLAGS=-O2 -Dlinux -DLINUX_PAM \ + -ansi -D_POSIX_SOURCE -Wall -Wwrite-strings \ + -Wpointer-arith -Wcast-qual -Wcast-align -Wtraditional \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Winline \ + -Wshadow -pedantic -fPIC +export MKDIR=mkdir -p +export LD_D=gcc -shared -Xlinker -x +endif + +# + +TITLE=pam_deny + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_deny/README b/modules/pam_deny/README new file mode 100644 index 00000000..6683bdcc --- /dev/null +++ b/modules/pam_deny/README @@ -0,0 +1,4 @@ +# $Id$ +# + +this module always fails, it ignores all options. diff --git a/modules/pam_deny/pam_deny.c b/modules/pam_deny/pam_deny.c new file mode 100644 index 00000000..123cb21b --- /dev/null +++ b/modules/pam_deny/pam_deny.c @@ -0,0 +1,100 @@ +/* pam_permit module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:33 agmorgan + * Initial revision + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.4 1997/02/15 19:05:15 morgan + * fixed email + * + * Revision 1.3 1996/06/02 08:06:19 morgan + * changes for new static protocol + * + * Revision 1.2 1996/05/26 04:01:12 morgan + * added static support + * + * Revision 1.1 1996/03/16 17:47:36 morgan + * Initial revision + * + */ + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD + +#include + +/* --- authentication management functions --- */ + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_AUTH_ERR; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_CRED_UNAVAIL; +} + +/* --- account management functions --- */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_ACCT_EXPIRED; +} + +/* --- password management --- */ + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_AUTHTOK_ERR; +} + +/* --- session management --- */ + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SYSTEM_ERR; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SYSTEM_ERR; +} + +/* end of module definition */ + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_deny_modstruct = { + "pam_deny", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; +#endif diff --git a/modules/pam_env/.cvsignore b/modules/pam_env/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_env/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_env/Makefile b/modules/pam_env/Makefile new file mode 100644 index 00000000..e872d1ee --- /dev/null +++ b/modules/pam_env/Makefile @@ -0,0 +1,111 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:33 agmorgan +# Initial revision +# +# Revision 1.2 1999/10/09 05:08:28 morgan +# removed libpwdb dependencies (libpwdb support is depreciated) +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.1 1997/04/05 06:42:35 morgan +# Initial revision +# +# Revision 1.1 1997/01/04 20:32:52 morgan +# Initial revision +# +# Created by Andrew Morgan 1996/12/8 +# Adaptations by Dave Kinclea and Cristian Gafton +# + +TITLE=pam_env + +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/pam_env.conf +export CONFILE + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(EXTRALIB) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(EXTRALIB) +endif + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SCONFIGED) + bash -f ./install_conf + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_env/README b/modules/pam_env/README new file mode 100644 index 00000000..04df323b --- /dev/null +++ b/modules/pam_env/README @@ -0,0 +1,72 @@ +# $Date$ +# $Author$ +# $Id$ +# +# This is the configuration file for pam_env, a PAM module to load in +# a configurable list of environment variables for a +# +# The original idea for this came from Andrew G. Morgan ... +# +# Mmm. Perhaps you might like to write a pam_env module that reads a +# default environment from a file? I can see that as REALLY +# useful... Note it would be an "auth" module that returns PAM_IGNORE +# for the auth part and sets the environment returning PAM_SUCCESS in +# the setcred function... +# +# +# What I wanted was the REMOTEHOST variable set, purely for selfish +# reasons, and AGM didn't want it added to the SimpleApps login +# program (which is where I added the patch). So, my first concern is +# that variable, from there there are numerous others that might/would +# be useful to be set: NNTPSERVER, LESS, PATH, PAGER, MANPAGER ..... +# +# Of course, these are a different kind of variable than REMOTEHOST in +# that they are things that are likely to be configured by +# administrators rather than set by logging in, how to treat them both +# in the same config file? +# +# Here is my idea: +# +# Each line starts with the variable name, there are then two possible +# options for each variable DEFAULT and OVERRIDE. +# DEFAULT allows and administrator to set the value of the +# variable to some default value, if none is supplied then the empty +# string is assumed. The OVERRIDE option tells pam_env that it should +# enter in its value (overriding the default value) if there is one +# to use. OVERRIDE is not used, "" is assumed and no override will be +# done. +# +# VARIABLE [DEFAULT=[value]] [OVERRIDE=[value]] +# +# (Possibly non-existent) environment variables may be used in values +# using the ${string} syntax and (possibly non-existent) PAM_ITEMs may +# be used in values using the @{string} syntax. Both the $ and @ +# characters can be backslash escaped to be used as literal values +# values can be delimited with "", escaped " not supported. +# +# +# First, some special variables +# +# Set the REMOTEHOST variable for any hosts that are remote, default +# to "localhost" rather than not being set at all +REMOTEHOST DEFAULT=localhost OVERRIDE=@{PAM_RHOST} +# +# Set the DISPLAY variable if it seems reasonable +DISPLAY DEFAULT=${REMOTEHOST}:0.0 OVERRIDE=${DISPLAY} +# +# +# Now some simple variables +# +PAGER DEFAULT=less +MANPAGER DEFAULT=less +LESS DEFAULT="M q e h15 z23 b80" +NNTPSERVER DEFAULT=localhost +PATH DEFAULT=${HOME}/bin:/usr/local/bin:/bin\ +:/usr/bin:/usr/local/bin/X11:/usr/bin/X11 +# +# silly examples of escaped variables, just to show how they work. +# +DOLLAR DEFAULT=\$ +DOLLARDOLLAR DEFAULT= OVERRIDE=\$${DOLLAR} +DOLLARPLUS DEFAULT=\${REMOTEHOST}${REMOTEHOST} +ATSIGN DEFAULT="" OVERRIDE=\@ diff --git a/modules/pam_env/install_conf b/modules/pam_env/install_conf new file mode 100755 index 00000000..4c608400 --- /dev/null +++ b/modules/pam_env/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +QUIET_INSTALL=../../.quiet_install +CONF=./pam_env.conf-example +MODULE=pam_env + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_env/pam_env.c b/modules/pam_env/pam_env.c new file mode 100644 index 00000000..5ba89268 --- /dev/null +++ b/modules/pam_env/pam_env.c @@ -0,0 +1,839 @@ +/* pam_mail module */ + +/* + * $Id$ + * + * Written by Dave Kinchlea 1997/01/31 + * Inspired by Andrew Morgan +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH /* This is primarily a AUTH_SETCRED module */ +#define PAM_SM_SESSION /* But I like to be friendly */ +#define PAM_SM_PASSWORD /* "" */ +#define PAM_SM_ACCOUNT /* "" */ + +#include +#include + +/* This little structure makes it easier to keep variables together */ + +typedef struct var { + char *name; + char *value; + char *defval; + char *override; +} VAR; + +#define BUF_SIZE 1024 +#define MAX_ENV 8192 + +#define GOOD_LINE 0 +#define BAD_LINE 100 /* This must be > the largest PAM_* error code */ + +#define DEFINE_VAR 101 +#define UNDEFINE_VAR 102 +#define ILLEGAL_VAR 103 + +static int _assemble_line(FILE *, char *, int); +static int _parse_line(char *, VAR *); +static int _check_var(pam_handle_t *, VAR *); /* This is the real meat */ +static void _clean_var(VAR *); +static int _expand_arg(pam_handle_t *, char **); +static const char * _pam_get_item_byname(pam_handle_t *, const char *); +static int _define_var(pam_handle_t *, VAR *); +static int _undefine_var(pam_handle_t *, VAR *); + +/* This is a flag used to designate an empty string */ +static char quote='Z'; + +/* some syslogging */ + +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-env", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 0x01 +#define PAM_NEW_CONF_FILE 0x02 +#define PAM_ENV_SILENT 0x04 +#define PAM_NEW_ENV_FILE 0x10 + +static int _pam_parse(int flags, int argc, const char **argv, char **conffile, + char **envfile, int *readenv) +{ + int ctrl=0; + + + /* step through arguments */ + for (; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strncmp(*argv,"conffile=",9)) { + *conffile = x_strdup(9+*argv); + if (*conffile != NULL) { + D(("new Configuration File: %s", *conffile)); + ctrl |= PAM_NEW_CONF_FILE; + } else { + _log_err(LOG_CRIT, + "Configuration file specification missing argument - ignored"); + } + } else if (!strncmp(*argv,"envfile=",8)) { + *envfile = x_strdup(8+*argv); + if (*envfile != NULL) { + D(("new Env File: %s", *envfile)); + ctrl |= PAM_NEW_ENV_FILE; + } else { + _log_err(LOG_CRIT, + "Env file specification missing argument - ignored"); + } + } else if (!strncmp(*argv,"readenv=",8)) + *readenv = atoi(8+*argv); + else + _log_err(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + + return ctrl; +} + +static int _parse_config_file(pam_handle_t *pamh, int ctrl, char **conffile) +{ + int retval; + const char *file; + char buffer[BUF_SIZE]; + FILE *conf; + VAR Var, *var=&Var; + + var->name=NULL; var->defval=NULL; var->override=NULL; + D(("Called.")); + + if (ctrl & PAM_NEW_CONF_FILE) { + file = *conffile; + } else { + file = DEFAULT_CONF_FILE; + } + + D(("Config file name is: %s", file)); + + /* + * Lets try to open the config file, parse it and process + * any variables found. + */ + + if ((conf = fopen(file,"r")) == NULL) { + _log_err(LOG_ERR, "Unable to open config file: %s", + strerror(errno)); + return PAM_IGNORE; + } + + /* _pam_assemble_line will provide a complete line from the config file, with all + * comments removed and any escaped newlines fixed up + */ + + while (( retval = _assemble_line(conf, buffer, BUF_SIZE)) > 0) { + D(("Read line: %s", buffer)); + + if ((retval = _parse_line(buffer, var)) == GOOD_LINE) { + retval = _check_var(pamh, var); + + if (DEFINE_VAR == retval) { + retval = _define_var(pamh, var); + + } else if (UNDEFINE_VAR == retval) { + retval = _undefine_var(pamh, var); + } + } + if (PAM_SUCCESS != retval && ILLEGAL_VAR != retval + && BAD_LINE != retval && PAM_BAD_ITEM != retval) break; + + _clean_var(var); + + } /* while */ + + (void) fclose(conf); + + /* tidy up */ + _clean_var(var); /* We could have got here prematurely, this is safe though */ + _pam_overwrite(*conffile); + _pam_drop(*conffile); + file = NULL; + D(("Exit.")); + return (retval<0?PAM_ABORT:PAM_SUCCESS); +} + +static int _parse_env_file(pam_handle_t *pamh, int ctrl, char **env_file) +{ + int retval=PAM_SUCCESS, i, t; + const char *file; + char buffer[BUF_SIZE], *key, *mark; + FILE *conf; + + if (ctrl & PAM_NEW_ENV_FILE) + file = *env_file; + else + file = DEFAULT_ETC_ENVFILE; + + D(("Env file name is: %s", file)); + + if ((conf = fopen(file,"r")) == NULL) { + D(("Unable to open env file: %s", strerror(errno))); + return PAM_ABORT; + } + + while (_assemble_line(conf, buffer, BUF_SIZE) > 0) { + D(("Read line: %s", buffer)); + key = buffer; + + /* skip leading white space */ + key += strspn(key, " \n\t"); + + /* skip blanks lines and comments */ + if (!key || key[0] == '#') + continue; + + /* skip over "export " if present so we can be compat with + bash type declerations */ + if (strncmp(key, "export ", (size_t) 7) == 0) + key += 7; + + /* now find the end of value */ + mark = key; + while(mark[0] != '\n' && mark[0] != '#' && mark[0] != '\0') + mark++; + if (mark[0] != '\0') + mark[0] = '\0'; + + /* + * sanity check, the key must be alpha-numeric + */ + + for ( i = 0 ; key[i] != '=' && key[i] != '\0' ; i++ ) + if (!isalnum(key[i]) && key[i] != '_') { + D(("key is not alpha numeric - '%s', ignoring", key)); + continue; + } + + /* now we try to be smart about quotes around the value, + but not too smart, we can't get all fancy with escaped + values like bash */ + if (key[i] == '=' && (key[++i] == '\"' || key[i] == '\'')) { + for ( t = i+1 ; key[t] != '\0' ; t++) + if (key[t] != '\"' && key[t] != '\'') + key[i++] = key[t]; + else if (key[t+1] != '\0') + key[i++] = key[t]; + key[i] = '\0'; + } + + /* set the env var, if it fails, we break out of the loop */ + retval = pam_putenv(pamh, key); + if (retval != PAM_SUCCESS) { + D(("error setting env \"%s\"", key)); + break; + } + } + + (void) fclose(conf); + + /* tidy up */ + _pam_overwrite(*env_file); + _pam_drop(*env_file); + file = NULL; + D(("Exit.")); + return (retval<0?PAM_IGNORE:PAM_SUCCESS); +} + +/* + * This is where we read a line of the PAM config file. The line may be + * preceeded by lines of comments and also extended with "\\\n" + */ + +static int _assemble_line(FILE *f, char *buffer, int buf_len) +{ + char *p = buffer; + char *s, *os; + int used = 0; + + /* loop broken with a 'break' when a non-'\\n' ended line is read */ + + D(("called.")); + for (;;) { + if (used >= buf_len) { + /* Overflow */ + D(("_assemble_line: overflow")); + return -1; + } + if (fgets(p, buf_len - used, f) == NULL) { + if (used) { + /* Incomplete read */ + return -1; + } else { + /* EOF */ + return 0; + } + } + + /* skip leading spaces --- line may be blank */ + + s = p + strspn(p, " \n\t"); + if (*s && (*s != '#')) { + os = s; + + /* + * we are only interested in characters before the first '#' + * character + */ + + while (*s && *s != '#') + ++s; + if (*s == '#') { + *s = '\0'; + used += strlen(os); + break; /* the line has been read */ + } + + s = os; + + /* + * Check for backslash by scanning back from the end of + * the entered line, the '\n' has been included since + * normally a line is terminated with this + * character. fgets() should only return one though! + */ + + s += strlen(s); + while (s > os && ((*--s == ' ') || (*s == '\t') + || (*s == '\n'))); + + /* check if it ends with a backslash */ + if (*s == '\\') { + *s = '\0'; /* truncate the line here */ + used += strlen(os); + p = s; /* there is more ... */ + } else { + /* End of the line! */ + used += strlen(os); + break; /* this is the complete line */ + } + + } else { + /* Nothing in this line */ + /* Don't move p */ + } + } + + return used; +} + +static int _parse_line(char *buffer, VAR *var) +{ + /* + * parse buffer into var, legal syntax is + * VARIABLE [DEFAULT=[[string]] [OVERRIDE=[value]] + * + * Any other options defined make this a bad line, + * error logged and no var set + */ + + int length, quoteflg=0; + char *ptr, **valptr, *tmpptr; + + D(("Called buffer = <%s>", buffer)); + + length = strcspn(buffer," \t\n"); + + if ((var->name = malloc(length + 1)) == NULL) { + _log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1); + return PAM_BUF_ERR; + } + + /* + * The first thing on the line HAS to be the variable name, + * it may be the only thing though. + */ + strncpy(var->name, buffer, length); + var->name[length] = '\0'; + D(("var->name = <%s>, length = %d", var->name, length)); + + /* + * Now we check for arguments, we only support two kinds and ('cause I am lazy) + * each one can actually be listed any number of times + */ + + ptr = buffer+length; + while ((length = strspn(ptr, " \t")) > 0) { + ptr += length; /* remove leading whitespace */ + D((ptr)); + if (strncmp(ptr,"DEFAULT=",8) == 0) { + ptr+=8; + D(("Default arg found: <%s>", ptr)); + valptr=&(var->defval); + } else if (strncmp(ptr, "OVERRIDE=", 9) == 0) { + ptr+=9; + D(("Override arg found: <%s>", ptr)); + valptr=&(var->override); + } else { + D(("Unrecognized options: <%s> - ignoring line", ptr)); + _log_err(LOG_ERR, "Unrecognized Option: %s - ignoring line", ptr); + return BAD_LINE; + } + + if ('"' != *ptr) { /* Escaped quotes not supported */ + length = strcspn(ptr, " \t\n"); + tmpptr = ptr+length; + } else { + tmpptr = strchr(++ptr, '"'); + if (!tmpptr) { + D(("Unterminated quoted string: %s", ptr-1)); + _log_err(LOG_ERR, "Unterminated quoted string: %s", ptr-1); + return BAD_LINE; + } + length = tmpptr - ptr; + if (*++tmpptr && ' ' != *tmpptr && '\t' != *tmpptr && '\n' != *tmpptr) { + D(("Quotes must cover the entire string: <%s>", ptr)); + _log_err(LOG_ERR, "Quotes must cover the entire string: <%s>", ptr); + return BAD_LINE; + } + quoteflg++; + } + if (length) { + if ((*valptr = malloc(length + 1)) == NULL) { + D(("Couldn't malloc %d bytes", length+1)); + _log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1); + return PAM_BUF_ERR; + } + (void)strncpy(*valptr,ptr,length); + (*valptr)[length]='\0'; + } else if (quoteflg--) { + *valptr = "e; /* a quick hack to handle the empty string */ + } + ptr = tmpptr; /* Start the search where we stopped */ + } /* while */ + + /* + * The line is parsed, all is well. + */ + + D(("Exit.")); + ptr = NULL; tmpptr = NULL; valptr = NULL; + return GOOD_LINE; +} + +static int _check_var(pam_handle_t *pamh, VAR *var) +{ + /* + * Examine the variable and determine what action to take. + * Returns DEFINE_VAR, UNDEFINE_VAR depending on action to take + * or a PAM_* error code if passed back from other routines + * + * if no DEFAULT provided, the empty string is assumed + * if no OVERRIDE provided, the empty string is assumed + * if DEFAULT= and OVERRIDE evaluates to the empty string, + * this variable should be undefined + * if DEFAULT="" and OVERRIDE evaluates to the empty string, + * this variable should be defined with no value + * if OVERRIDE=value and value turns into the empty string, DEFAULT is used + * + * If DEFINE_VAR is to be returned, the correct value to define will + * be pointed to by var->value + */ + + int retval; + + D(("Called.")); + + /* + * First thing to do is to expand any arguments, but only + * if they are not the special quote values (cause expand_arg + * changes memory). + */ + + if (var->defval && ("e != var->defval) && + ((retval = _expand_arg(pamh, &(var->defval))) != PAM_SUCCESS)) { + return retval; + } + if (var->override && ("e != var->override) && + ((retval = _expand_arg(pamh, &(var->override))) != PAM_SUCCESS)) { + return retval; + } + + /* Now its easy */ + + if (var->override && *(var->override) && "e != var->override) { + /* if there is a non-empty string in var->override, we use it */ + D(("OVERRIDE variable <%s> being used: <%s>", var->name, var->override)); + var->value = var->override; + retval = DEFINE_VAR; + } else { + + var->value = var->defval; + if ("e == var->defval) { + /* + * This means that the empty string was given for defval value + * which indicates that a variable should be defined with no value + */ + *var->defval = '\0'; + D(("An empty variable: <%s>", var->name)); + retval = DEFINE_VAR; + } else if (var->defval) { + D(("DEFAULT variable <%s> being used: <%s>", var->name, var->defval)); + retval = DEFINE_VAR; + } else { + D(("UNDEFINE variable <%s>", var->name)); + retval = UNDEFINE_VAR; + } + } + + D(("Exit.")); + return retval; +} + +static int _expand_arg(pam_handle_t *pamh, char **value) +{ + const char *orig=*value, *tmpptr=NULL; + char *ptr; /* + * Sure would be nice to use tmpptr but it needs to be + * a constant so that the compiler will shut up when I + * call pam_getenv and _pam_get_item_byname -- sigh + */ + + char type, tmpval[BUF_SIZE]; /* No unexpanded variable can be bigger than BUF_SIZE */ + char tmp[MAX_ENV]; /* I know this shouldn't be hard-coded but it's so + * much easier this way */ + + D(("Remember to initialize tmp!")); + tmp[0] = '\0'; + + /* + * (possibly non-existent) environment variables can be used as values + * by prepending a "$" and wrapping in {} (ie: ${HOST}), can escape with "\" + * (possibly non-existent) PAM items can be used as values + * by prepending a "@" and wrapping in {} (ie: @{PAM_RHOST}, can escape + * + */ + D(("Expanding <%s>",orig)); + while (*orig) { /* while there is some input to deal with */ + if ('\\' == *orig) { + ++orig; + if ('$' != *orig && '@' != *orig) { + D(("Unrecognized escaped character: <%c> - ignoring", *orig)); + _log_err(LOG_ERR, "Unrecognized escaped character: <%c> - ignoring", + *orig); + } else if ((strlen(tmp) + 1) < MAX_ENV) { + tmp[strlen(tmp)] = *orig++; /* Note the increment */ + } else { + /* is it really a good idea to try to log this? */ + D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr)); + _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr); + } + continue; + } + if ('$' == *orig || '@' == *orig) { + if ('{' != *(orig+1)) { + D(("Expandable variables must be wrapped in {} <%s> - ignoring", orig)); + _log_err(LOG_ERR, "Expandable variables must be wrapped in {} <%s> - ignoring", + orig); + if ((strlen(tmp) + 1) < MAX_ENV) { + tmp[strlen(tmp)] = *orig++; /* Note the increment */ + } + continue; + } else { + D(("Expandable argument: <%s>", orig)); + type = *orig; + orig+=2; /* skip the ${ or @{ characters */ + ptr = strchr(orig, '}'); + if (ptr) { + *ptr++ = '\0'; + } else { + D(("Unterminated expandable variable: <%s>", orig-2)); + _log_err(LOG_ERR, "Unterminated expandable variable: <%s>", orig-2); + return PAM_ABORT; + } + strncpy(tmpval, orig, (size_t) BUF_SIZE); + orig=ptr; + /* + * so, we know we need to expand tmpval, it is either + * an environment variable or a PAM_ITEM. type will tell us which + */ + switch (type) { + + case '$': + D(("Expanding env var: <%s>",tmpval)); + tmpptr = pam_getenv(pamh, tmpval); + D(("Expanded to <%s>", tmpptr)); + break; + + case '@': + D(("Expanding pam item: <%s>",tmpval)); + tmpptr = _pam_get_item_byname(pamh, tmpval); + D(("Expanded to <%s>", tmpptr)); + break; + + default: + D(("Impossible error, type == <%c>", type)); + _log_err(LOG_CRIT, "Impossible error, type == <%c>", type); + return PAM_ABORT; + } /* switch */ + + if (tmpptr) { + if ((strlen(tmp) + strlen(tmpptr)) < MAX_ENV) { + strcat(tmp, tmpptr); + } else { + /* is it really a good idea to try to log this? */ + D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr)); + _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr); + } + } + } /* if ('{' != *orig++) */ + } else { /* if ( '$' == *orig || '@' == *orig) */ + if ((strlen(tmp) + 1) < MAX_ENV) { + tmp[strlen(tmp)] = *orig++; /* Note the increment */ + } else { + /* is it really a good idea to try to log this? */ + D(("Variable buffer overflow: <%s> + <%s>", tmp, tmpptr)); + _log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr); + } + } + } /* for (;*orig;) */ + + if (strlen(tmp) > strlen(*value)) { + free(*value); + if ((*value = malloc(strlen(tmp) +1)) == NULL) { + D(("Couldn't malloc %d bytes for expanded var", strlen(tmp)+1)); + _log_err(LOG_ERR,"Couldn't malloc %d bytes for expanded var", + strlen(tmp)+1); + return PAM_BUF_ERR; + } + } + strcpy(*value, tmp); + memset(tmp,'\0',sizeof(tmp)); + D(("Exit.")); + + return PAM_SUCCESS; +} + +static const char * _pam_get_item_byname(pam_handle_t *pamh, const char *name) +{ + /* + * This function just allows me to use names as given in the config + * file and translate them into the appropriate PAM_ITEM macro + */ + + int item; + const char *itemval; + + D(("Called.")); + if (strcmp(name, "PAM_USER") == 0) { + item = PAM_USER; + } else if (strcmp(name, "PAM_USER_PROMPT") == 0) { + item = PAM_USER_PROMPT; + } else if (strcmp(name, "PAM_TTY") == 0) { + item = PAM_TTY; + } else if (strcmp(name, "PAM_RUSER") == 0) { + item = PAM_RUSER; + } else if (strcmp(name, "PAM_RHOST") == 0) { + item = PAM_RHOST; + } else { + D(("Unknown PAM_ITEM: <%s>", name)); + _log_err(LOG_ERR, "Unknown PAM_ITEM: <%s>", name); + return NULL; + } + + if (pam_get_item(pamh, item, (const void **)&itemval) != PAM_SUCCESS) { + D(("pam_get_item failed")); + return NULL; /* let pam_get_item() log the error */ + } + D(("Exit.")); + return itemval; +} + +static int _define_var(pam_handle_t *pamh, VAR *var) +{ + /* We have a variable to define, this is a simple function */ + + char *envvar; + int size, retval=PAM_SUCCESS; + + D(("Called.")); + size = strlen(var->name)+strlen(var->value)+2; + if ((envvar = malloc(size)) == NULL) { + D(("Malloc fail, size = %d", size)); + _log_err(LOG_ERR, "Malloc fail, size = %d", size); + return PAM_BUF_ERR; + } + (void) sprintf(envvar,"%s=%s",var->name,var->value); + retval = pam_putenv(pamh, envvar); + free(envvar); envvar=NULL; + D(("Exit.")); + return retval; +} + +static int _undefine_var(pam_handle_t *pamh, VAR *var) +{ + /* We have a variable to undefine, this is a simple function */ + + D(("Called and exit.")); + return pam_putenv(pamh, var->name); +} + +static void _clean_var(VAR *var) +{ + if (var->name) { + free(var->name); + } + if (var->defval && ("e != var->defval)) { + free(var->defval); + } + if (var->override && ("e != var->override)) { + free(var->override); + } + var->name = NULL; + var->value = NULL; /* never has memory specific to it */ + var->defval = NULL; + var->override = NULL; + return; +} + + + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + int retval, ctrl, readenv=DEFAULT_READ_ENVFILE; + char *conf_file=NULL, *env_file=NULL; + + /* + * this module sets environment variables read in from a file + */ + + D(("Called.")); + ctrl = _pam_parse(flags, argc, argv, &conf_file, &env_file, &readenv); + + retval = _parse_config_file(pamh, ctrl, &conf_file); + + if(readenv) + _parse_env_file(pamh, ctrl, &env_file); + + /* indicate success or failure */ + + D(("Exit.")); + return retval; +} + +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + _log_err(LOG_NOTICE, "pam_sm_acct_mgmt called inappropriatly"); + return PAM_SERVICE_ERR; +} + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int retval, ctrl, readenv=DEFAULT_READ_ENVFILE; + char *conf_file=NULL, *env_file=NULL; + + /* + * this module sets environment variables read in from a file + */ + + D(("Called.")); + ctrl = _pam_parse(flags, argc, argv, &conf_file, &env_file, &readenv); + + retval = _parse_config_file(pamh, ctrl, &conf_file); + + if(readenv) + _parse_env_file(pamh, ctrl, &env_file); + + /* indicate success or failure */ + + D(("Exit.")); + return retval; +} + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc, + const char **argv) +{ + D(("Called and Exit")); + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + _log_err(LOG_NOTICE, "pam_sm_chauthtok called inappropriatly"); + return PAM_SERVICE_ERR; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_env_modstruct = { + "pam_env", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_env/pam_env.conf-example b/modules/pam_env/pam_env.conf-example new file mode 100644 index 00000000..c2c4333f --- /dev/null +++ b/modules/pam_env/pam_env.conf-example @@ -0,0 +1,72 @@ +# $Date$ +# $Author$ +# $Id$ +# +# This is the configuration file for pam_env, a PAM module to load in +# a configurable list of environment variables for a +# +# The original idea for this came from Andrew G. Morgan ... +# +# Mmm. Perhaps you might like to write a pam_env module that reads a +# default environment from a file? I can see that as REALLY +# useful... Note it would be an "auth" module that returns PAM_IGNORE +# for the auth part and sets the environment returning PAM_SUCCESS in +# the setcred function... +# +# +# What I wanted was the REMOTEHOST variable set, purely for selfish +# reasons, and AGM didn't want it added to the SimpleApps login +# program (which is where I added the patch). So, my first concern is +# that variable, from there there are numerous others that might/would +# be useful to be set: NNTPSERVER, LESS, PATH, PAGER, MANPAGER ..... +# +# Of course, these are a different kind of variable than REMOTEHOST in +# that they are things that are likely to be configured by +# administrators rather than set by logging in, how to treat them both +# in the same config file? +# +# Here is my idea: +# +# Each line starts with the variable name, there are then two possible +# options for each variable DEFAULT and OVERRIDE. +# DEFAULT allows and administrator to set the value of the +# variable to some default value, if none is supplied then the empty +# string is assumed. The OVERRIDE option tells pam_env that it should +# enter in its value (overriding the default value) if there is one +# to use. OVERRIDE is not used, "" is assumed and no override will be +# done. +# +# VARIABLE [DEFAULT=[value]] [OVERRIDE=[value]] +# +# (Possibly non-existent) environment variables may be used in values +# using the ${string} syntax and (possibly non-existent) PAM_ITEMs may +# be used in values using the @{string} syntax. Both the $ and @ +# characters can be backslash escaped to be used as literal values +# values can be delimited with "", escaped " not supported. +# +# +# First, some special variables +# +# Set the REMOTEHOST variable for any hosts that are remote, default +# to "localhost" rather than not being set at all +#REMOTEHOST DEFAULT=localhost OVERRIDE=@{PAM_RHOST} +# +# Set the DISPLAY variable if it seems reasonable +#DISPLAY DEFAULT=${REMOTEHOST}:0.0 OVERRIDE=${DISPLAY} +# +# +# Now some simple variables +# +#PAGER DEFAULT=less +#MANPAGER DEFAULT=less +#LESS DEFAULT="M q e h15 z23 b80" +#NNTPSERVER DEFAULT=localhost +#PATH DEFAULT=${HOME}/bin:/usr/local/bin:/bin\ +#:/usr/bin:/usr/local/bin/X11:/usr/bin/X11 +# +# silly examples of escaped variables, just to show how they work. +# +#DOLLAR DEFAULT=\$ +#DOLLARDOLLAR DEFAULT= OVERRIDE=\$${DOLLAR} +#DOLLARPLUS DEFAULT=\${REMOTEHOST}${REMOTEHOST} +#ATSIGN DEFAULT="" OVERRIDE=\@ diff --git a/modules/pam_filter/.cvsignore b/modules/pam_filter/.cvsignore new file mode 100644 index 00000000..877dafe0 --- /dev/null +++ b/modules/pam_filter/.cvsignore @@ -0,0 +1,2 @@ +dynamic +security diff --git a/modules/pam_filter/.upperLOWER b/modules/pam_filter/.upperLOWER new file mode 100644 index 00000000..2531b468 --- /dev/null +++ b/modules/pam_filter/.upperLOWER @@ -0,0 +1 @@ +a test filter that transposes upper and lower case characters diff --git a/modules/pam_filter/Makefile b/modules/pam_filter/Makefile new file mode 100644 index 00000000..f4db56a9 --- /dev/null +++ b/modules/pam_filter/Makefile @@ -0,0 +1,140 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/3/11 +# + +ifeq ($(OS),solaris) + +include ../dont_makefile + +else + +TITLE=pam_filter +FILTERS=upperLOWER +FILTERSDIR=$(SUPLEMENTED)/pam_filter +export FILTERSDIR + +CFLAGS += -I. -I.. +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +# +# this is where we compile this module +# + +all: dirs $(LIBSHARED) $(LIBSTATIC) register filters + +dirs: + if [ ! -f security ]; then ln -sf include security ; fi +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +filters: + @for i in $(FILTERS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i all ; \ + fi ; \ + done + + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) +endif + +ifdef DYNAMIC +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) +endif + +ifdef STATIC +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + @for i in $(FILTERS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i install ; \ + fi ; \ + done + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(INCLUDED) + $(INSTALL) -m 644 include/pam_filter.h $(FAKEROOT)$(INCLUDED) + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(INCLUDED)/pam_filter.h + @for i in $(FILTERS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i remove ; \ + fi ; \ + done + +lclean: + rm -f $(LIBSHARED) $(LIBOBJD) $(LIBOBJS) core *~ + +clean: lclean + @for i in $(FILTERS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i clean ; \ + fi ; \ + done + +extraclean: lclean + @rm -f security + @rm -f *.a *.o *.so *.bak + for i in $(FILTERS) ; do \ + if [ -d $$i ]; then \ + $(MAKE) -C $$i extraclean ; \ + fi ; \ + done + +security: + ln -s include security + +endif diff --git a/modules/pam_filter/README b/modules/pam_filter/README new file mode 100644 index 00000000..850f1145 --- /dev/null +++ b/modules/pam_filter/README @@ -0,0 +1,94 @@ +# +# $Id$ +# +# This describes the behavior of this module with respect to the +# /etc/pam.conf file. +# +# writen by Andrew Morgan +# + +This module is intended to be a platform for providing access to all +of the input/output that passes between the user and the application. +It is only suitable for tty-based and (stdin/stdout) applications. And +is only known to work on Linux based systems. + +The action of the module is dictated by the arguments it is given in +the pam.conf file. + +recognized flags are: + + debug print some information to syslog(3) + + new_term set the PAM_TTY item to the new filtered + terminal (the default is to set it + to be that of the users terminal) + + non_term don't try to set the PAM_TTY item + + run1/run2 these arguments indicate that the + module should separate the application + from the user and insert a filter + program between them. The pathname of + the filter program follows the 'runN' + argument. Arguments that follow this + pathname are passed as arguments to + the filter program. + + The distinction between run1 and run2 + is which of the two functions of + the given management-type triggers the + execution of the indicated filter. + + type: run1 run2 + ----- ---- ---- + + auth pam_sm_authenticate pam_sm_setcred + + account [ pam_sm_acct_mgmt (either is good) ] + + session pam_sm_open_session pam_sm_close_session + + password pam_sm_chauthtok/PRELIM pam_sm_chauthtok/UPDATE + +Note, in the case of 'password' PRELIM/UPDATE indicates which of the +two calls to pam_sm_chauthtok from libpam (not the application) will +trigger the filter. + +What a filter program should expect: +------------------------------------ + +Definitions for filter programs (which may be locally designed) are +contained in the file. + +Arguments are not passed to the filter on the command line, since this +is plainly visible when a user types 'ps -a'. Instead they are passed +as the filter's environment. Other information is passed in this way +too. + +Here is a list of the environment variables that a filter should +expect: + + ARGS="filter_path_name argument list" + SERVICE="service_name" (as it appears in /etc/pam.conf) + USER="username" + TYPE="module_fn" (the name of the function in pam_filter.so + that invoked the filter) + +[This list is likely to grow. If you want something added, email me!] + +Among other things this module is intended to provide a useful means +of logging the activity of users in as discrete a manner as possible. + +Existing filters: +----------------- + +Currently, there is a single supplied filter (upperLOWER). The effect +of using this filter is to transpose upper and lower case letters +between the user and the application. This is really annoying when you +try the 'xsh' example application! ;) + +TODO: provide more filters... + Decide if providing stderr interception is really overkill. + +Andrew G. Morgan 1996/5/27 + diff --git a/modules/pam_filter/include/pam_filter.h b/modules/pam_filter/include/pam_filter.h new file mode 100644 index 00000000..630198ee --- /dev/null +++ b/modules/pam_filter/include/pam_filter.h @@ -0,0 +1,32 @@ +/* + * $Id$ + * + * this file is associated with the Linux-PAM filter module. + * it was written by Andrew G. Morgan + * + */ + +#ifndef PAM_FILTER_H +#define PAM_FILTER_H + +#include + +/* + * this will fail if there is some problem with these file descriptors + * being allocated by the pam_filter Linux-PAM module. The numbers + * here are thought safe, but the filter developer should use the + * macros, as these numbers are subject to change. + * + * The APPXXX_FILENO file descriptors are the STDIN/OUT/ERR_FILENO of the + * application. The filter uses the STDIN/OUT/ERR_FILENO's to converse + * with the user, passes (modified) user input to the application via + * APPIN_FILENO, and receives application output from APPOUT_FILENO/ERR. + */ + +#define APPIN_FILENO 3 /* write here to give application input */ +#define APPOUT_FILENO 4 /* read here to get application output */ +#define APPERR_FILENO 5 /* read here to get application errors */ + +#define APPTOP_FILE 6 /* used by select */ + +#endif diff --git a/modules/pam_filter/pam_filter.c b/modules/pam_filter/pam_filter.c new file mode 100644 index 00000000..a1ba10b0 --- /dev/null +++ b/modules/pam_filter/pam_filter.c @@ -0,0 +1,738 @@ +/* + * $Id$ + * + * written by Andrew Morgan with much help from + * Richard Stevens' UNIX Network Programming book. + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD + +#include +#include + +/* ------ some tokens used for convenience throughout this file ------- */ + +#define FILTER_DEBUG 01 +#define FILTER_RUN1 02 +#define FILTER_RUN2 04 +#define NEW_TERM 010 +#define NON_TERM 020 + +/* -------------------------------------------------------------------- */ + +/* log errors */ + +#include + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("pam_filter", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +#define TERMINAL_LEN 12 + +static int master(char *terminal) +/* + * try to open all of the terminals in sequence return first free one, + * or -1 + */ +{ + const char ptys[] = "pqrs", *pty = ptys; + const char hexs[] = "0123456789abcdef", *hex; + struct stat tstat; + int fd; + + strcpy(terminal, "/dev/pty??"); + + while (*pty) { /* step through four types */ + terminal[8] = *pty++; + terminal[9] = '0'; + if (stat(terminal,&tstat) < 0) { + _pam_log(LOG_WARNING, "unknown pseudo terminal; %s", terminal); + break; + } + for (hex = hexs; *hex; ) { /* step through 16 of these */ + terminal[9] = *hex++; + if ((fd = open(terminal, O_RDWR)) >= 0) { + return fd; + } + } + } + + /* no terminal found */ + + return -1; +} + +static int process_args(pam_handle_t *pamh + , int argc, const char **argv, const char *type + , char ***evp, const char **filtername) +{ + int ctrl=0; + + while (argc-- > 0) { + if (strcmp("debug",*argv) == 0) { + ctrl |= FILTER_DEBUG; + } else if (strcmp("new_term",*argv) == 0) { + ctrl |= NEW_TERM; + } else if (strcmp("non_term",*argv) == 0) { + ctrl |= NON_TERM; + } else if (strcmp("run1",*argv) == 0) { + ctrl |= FILTER_RUN1; + if (argc <= 0) { + _pam_log(LOG_ALERT,"no run filter supplied"); + } else + break; + } else if (strcmp("run2",*argv) == 0) { + ctrl |= FILTER_RUN2; + if (argc <= 0) { + _pam_log(LOG_ALERT,"no run filter supplied"); + } else + break; + } else { + _pam_log(LOG_ERR, "unrecognized option: %s (ignored)", *argv); + } + ++argv; /* step along list */ + } + + if (argc < 0) { + /* there was no reference to a filter */ + *filtername = NULL; + *evp = NULL; + } else { + char **levp; + const char *tmp; + int i,size; + + *filtername = *++argv; + if (ctrl & FILTER_DEBUG) { + _pam_log(LOG_DEBUG,"will run filter %s\n", *filtername); + } + + levp = (char **) malloc(5*sizeof(char *)); + if (levp == NULL) { + _pam_log(LOG_CRIT,"no memory for environment of filter"); + return -1; + } + + for (size=i=0; i terminate */ + + /* the "SERVICE" variable */ + +#define SERVICE_OFFSET 8 /* sizeof("SERVICE="); */ +#define SERVICE_NAME "SERVICE=" + + pam_get_item(pamh, PAM_SERVICE, (const void **)&tmp); + size = SERVICE_OFFSET+strlen(tmp); + + levp[1] = (char *) malloc(size+1); + if (levp[1] == NULL) { + _pam_log(LOG_CRIT,"no memory for service name"); + if (levp) { + free(levp[0]); + free(levp); + } + return -1; + } + + strncpy(levp[1],SERVICE_NAME,SERVICE_OFFSET); + strcpy(levp[1]+SERVICE_OFFSET, tmp); + levp[1][size] = '\0'; /* terminate */ + + /* the "USER" variable */ + +#define USER_OFFSET 5 /* sizeof("USER="); */ +#define USER_NAME "USER=" + + pam_get_user(pamh, &tmp, NULL); + if (tmp == NULL) { + tmp = ""; + } + size = USER_OFFSET+strlen(tmp); + + levp[2] = (char *) malloc(size+1); + if (levp[2] == NULL) { + _pam_log(LOG_CRIT,"no memory for user's name"); + if (levp) { + free(levp[1]); + free(levp[0]); + free(levp); + } + return -1; + } + + strncpy(levp[2],USER_NAME,USER_OFFSET); + strcpy(levp[2]+USER_OFFSET, tmp); + levp[2][size] = '\0'; /* terminate */ + + /* the "USER" variable */ + +#define TYPE_OFFSET 5 /* sizeof("TYPE="); */ +#define TYPE_NAME "TYPE=" + + size = TYPE_OFFSET+strlen(type); + + levp[3] = (char *) malloc(size+1); + if (levp[3] == NULL) { + _pam_log(LOG_CRIT,"no memory for type"); + if (levp) { + free(levp[2]); + free(levp[1]); + free(levp[0]); + free(levp); + } + return -1; + } + + strncpy(levp[3],TYPE_NAME,TYPE_OFFSET); + strcpy(levp[3]+TYPE_OFFSET, type); + levp[3][size] = '\0'; /* terminate */ + + levp[4] = NULL; /* end list */ + + *evp = levp; + } + + if ((ctrl & FILTER_DEBUG) && *filtername) { + char **e; + + _pam_log(LOG_DEBUG,"filter[%s]: %s",type,*filtername); + _pam_log(LOG_DEBUG,"environment:"); + for (e=*evp; e && *e; ++e) { + _pam_log(LOG_DEBUG," %s",*e); + } + } + + return ctrl; +} + +static void free_evp(char *evp[]) +{ + int i; + + if (evp) + for (i=0; i<4; ++i) { + if (evp[i]) + free(evp[i]); + } + free(evp); +} + +static int set_filter(pam_handle_t *pamh, int flags, int ctrl + , const char **evp, const char *filtername) +{ + int status=-1; + char terminal[TERMINAL_LEN]; + struct termio stored_mode; /* initial terminal mode settings */ + int fd[2], child=0, child2=0, aterminal; + + if (filtername == NULL || *filtername != '/') { + _pam_log(LOG_ALERT, "filtername not permitted; require full path"); + return PAM_ABORT; + } + + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { + aterminal = 0; + } else { + aterminal = 1; + } + + if (aterminal) { + + /* open the master pseudo terminal */ + + fd[0] = master(terminal); + if (fd[0] < 0) { + _pam_log(LOG_CRIT,"no master terminal"); + return PAM_AUTH_ERR; + } + + /* set terminal into raw mode.. remember old mode so that we can + revert to it after the child has quit. */ + + /* this is termio terminal handling... */ + + if (ioctl(STDIN_FILENO, TCGETA, (char *) &stored_mode ) < 0) { + /* in trouble, so close down */ + close(fd[0]); + _pam_log(LOG_CRIT, "couldn't copy terminal mode"); + return PAM_ABORT; + } else { + struct termio t_mode = stored_mode; + + t_mode.c_iflag = 0; /* no input control */ + t_mode.c_oflag &= ~OPOST; /* no ouput post processing */ + + /* no signals, canonical input, echoing, upper/lower output */ + t_mode.c_lflag &= ~(ISIG|ICANON|ECHO|XCASE); + t_mode.c_cflag &= ~(CSIZE|PARENB); /* no parity */ + t_mode.c_cflag |= CS8; /* 8 bit chars */ + + t_mode.c_cc[VMIN] = 1; /* number of chars to satisfy a read */ + t_mode.c_cc[VTIME] = 0; /* 0/10th second for chars */ + + if (ioctl(STDIN_FILENO, TCSETA, (char *) &t_mode) < 0) { + close(fd[0]); + _pam_log(LOG_WARNING, "couldn't put terminal in RAW mode"); + return PAM_ABORT; + } + + /* + * NOTE: Unlike the stream socket case here the child + * opens the slave terminal as fd[1] *after* the fork... + */ + } + } else { + + /* + * not a terminal line so just open a stream socket fd[0-1] + * both set... + */ + + if ( socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0 ) { + _pam_log(LOG_CRIT,"couldn't open a stream pipe"); + return PAM_ABORT; + } + } + + /* start child process */ + + if ( (child = fork()) < 0 ) { + + _pam_log(LOG_WARNING,"first fork failed"); + if (aterminal) { + (void) ioctl(STDIN_FILENO, TCSETA, (char *) &stored_mode); + } + + return PAM_AUTH_ERR; + } + + if ( child == 0 ) { /* child process *is* application */ + + if (aterminal) { + + /* close the controlling tty */ + +#if defined(__hpux) && defined(O_NOCTTY) + int t = open("/dev/tty", O_RDWR|O_NOCTTY); +#else + int t = open("/dev/tty",O_RDWR); + if (t > 0) { + (void) ioctl(t, TIOCNOTTY, NULL); + close(t); + } +#endif /* defined(__hpux) && defined(O_NOCTTY) */ + + /* make this process it's own process leader */ + if (setsid() == -1) { + _pam_log(LOG_WARNING,"child cannot become new session"); + return PAM_ABORT; + } + + /* find slave's name */ + terminal[5] = 't'; /* want to open slave terminal */ + fd[1] = open(terminal, O_RDWR); + close(fd[0]); /* process is the child -- uses line fd[1] */ + + if (fd[1] < 0) { + _pam_log(LOG_WARNING,"cannot open slave terminal; %s" + ,terminal); + return PAM_ABORT; + } + + /* initialize the child's terminal to be the way the + parent's was before we set it into RAW mode */ + + if (ioctl(fd[1], TCSETA, (char *) &stored_mode) < 0) { + _pam_log(LOG_WARNING,"cannot set slave terminal mode; %s" + ,terminal); + close(fd[1]); + return PAM_ABORT; + } + + } else { + + /* nothing to do for a simple stream socket */ + + } + + /* re-assign the stdin/out to fd[1] <- (talks to filter). */ + + if ( dup2(fd[1],STDIN_FILENO) != STDIN_FILENO || + dup2(fd[1],STDOUT_FILENO) != STDOUT_FILENO || + dup2(fd[1],STDERR_FILENO) != STDERR_FILENO ) { + _pam_log(LOG_WARNING + ,"unable to re-assign STDIN/OUT/ERR...'s"); + close(fd[1]); + return PAM_ABORT; + } + + /* make sure that file descriptors survive 'exec's */ + + if ( fcntl(STDIN_FILENO, F_SETFD, 0) || + fcntl(STDOUT_FILENO,F_SETFD, 0) || + fcntl(STDERR_FILENO,F_SETFD, 0) ) { + _pam_log(LOG_WARNING + ,"unable to re-assign STDIN/OUT/ERR...'s"); + return PAM_ABORT; + } + + /* now the user input is read from the parent/filter: forget fd */ + + close(fd[1]); + + /* the current process is now aparently working with filtered + stdio/stdout/stderr --- success! */ + + return PAM_SUCCESS; + } + + /* + * process is the parent here. So we can close the application's + * input/output + */ + + close(fd[1]); + + /* Clear out passwords... there is a security problem here in + * that this process never executes pam_end. Consequently, any + * other sensitive data in this process is *not* explicitly + * overwritten, before the process terminates */ + + (void) pam_set_item(pamh, PAM_AUTHTOK, NULL); + (void) pam_set_item(pamh, PAM_OLDAUTHTOK, NULL); + + /* fork a copy of process to run the actual filter executable */ + + if ( (child2 = fork()) < 0 ) { + + _pam_log(LOG_WARNING,"filter fork failed"); + child2 = 0; + + } else if ( child2 == 0 ) { /* exec the child filter */ + + if ( dup2(fd[0],APPIN_FILENO) != APPIN_FILENO || + dup2(fd[0],APPOUT_FILENO) != APPOUT_FILENO || + dup2(fd[0],APPERR_FILENO) != APPERR_FILENO ) { + _pam_log(LOG_WARNING + ,"unable to re-assign APPIN/OUT/ERR...'s"); + close(fd[0]); + exit(1); + } + + /* make sure that file descriptors survive 'exec's */ + + if ( fcntl(APPIN_FILENO, F_SETFD, 0) == -1 || + fcntl(APPOUT_FILENO,F_SETFD, 0) == -1 || + fcntl(APPERR_FILENO,F_SETFD, 0) == -1 ) { + _pam_log(LOG_WARNING + ,"unable to retain APPIN/OUT/ERR...'s"); + close(APPIN_FILENO); + close(APPOUT_FILENO); + close(APPERR_FILENO); + exit(1); + } + + /* now the user input is read from the parent through filter */ + + execle(filtername, "", NULL, evp); + + /* getting to here is an error */ + + _pam_log(LOG_ALERT, "filter: %s, not executable", filtername); + + } else { /* wait for either of the two children to exit */ + + while (child && child2) { /* loop if there are two children */ + int lstatus=0; + int chid; + + chid = wait(&lstatus); + if (chid == child) { + + if (WIFEXITED(lstatus)) { /* exited ? */ + status = WEXITSTATUS(lstatus); + } else if (WIFSIGNALED(lstatus)) { /* killed ? */ + status = -1; + } else + continue; /* just stopped etc.. */ + child = 0; /* the child has exited */ + + } else if (chid == child2) { + /* + * if the filter has exited. Let the child die + * naturally below + */ + if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus)) + child2 = 0; + } else { + + _pam_log(LOG_ALERT + ,"programming error : " + __FILE__ " line %d" + , lstatus, __LINE__ ); + child = child2 = 0; + status = -1; + + } + } + } + + close(fd[0]); + + /* if there is something running, wait for it to exit */ + + while (child || child2) { + int lstatus=0; + int chid; + + chid = wait(&lstatus); + + if (child && chid == child) { + + if (WIFEXITED(lstatus)) { /* exited ? */ + status = WEXITSTATUS(lstatus); + } else if (WIFSIGNALED(lstatus)) { /* killed ? */ + status = -1; + } else + continue; /* just stopped etc.. */ + child = 0; /* the child has exited */ + + } else if (child2 && chid == child2) { + + if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus)) + child2 = 0; + + } else { + + _pam_log(LOG_ALERT + ,"programming error : " + __FILE__ " line %d" + , lstatus, __LINE__ ); + child = child2 = 0; + status = -1; + + } + } + + if (aterminal) { + /* reset to initial terminal mode */ + (void) ioctl(STDIN_FILENO, TCSETA, (char *) &stored_mode); + } + + if (ctrl & FILTER_DEBUG) { + _pam_log(LOG_DEBUG,"parent process exited"); /* clock off */ + } + + /* quit the parent process, returning the child's exit status */ + + exit(status); +} + +static int set_the_terminal(pam_handle_t *pamh) +{ + const char *tty; + + if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS + || tty == NULL) { + tty = ttyname(STDIN_FILENO); + if (tty == NULL) { + _pam_log(LOG_ERR, "couldn't get the tty name"); + return PAM_ABORT; + } + if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) { + _pam_log(LOG_ERR, "couldn't set tty name"); + return PAM_ABORT; + } + } + return PAM_SUCCESS; +} + +static int need_a_filter(pam_handle_t *pamh + , int flags, int argc, const char **argv + , const char *name, int which_run) +{ + int ctrl; + char **evp; + const char *filterfile; + int retval; + + ctrl = process_args(pamh, argc, argv, name, &evp, &filterfile); + if (ctrl == -1) { + return PAM_AUTHINFO_UNAVAIL; + } + + /* set the tty to the old or the new one? */ + + if (!(ctrl & NON_TERM) && !(ctrl & NEW_TERM)) { + retval = set_the_terminal(pamh); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "tried and failed to set PAM_TTY"); + } + } else { + retval = PAM_SUCCESS; /* nothing to do which is always a success */ + } + + if (retval == PAM_SUCCESS && (ctrl & which_run)) { + retval = set_filter(pamh, flags, ctrl + , (const char **)evp, filterfile); + } + + if (retval == PAM_SUCCESS + && !(ctrl & NON_TERM) && (ctrl & NEW_TERM)) { + retval = set_the_terminal(pamh); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR + , "tried and failed to set new terminal as PAM_TTY"); + } + } + + free_evp(evp); + + if (ctrl & FILTER_DEBUG) { + _pam_log(LOG_DEBUG, "filter/%s, returning %d", name, retval); + _pam_log(LOG_DEBUG, "[%s]", pam_strerror(pamh, retval)); + } + + return retval; +} + +/* ----------------- public functions ---------------- */ + +/* + * here are the advertised access points ... + */ + +/* ------------------ authentication ----------------- */ + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh + , int flags, int argc, const char **argv) +{ + return need_a_filter(pamh, flags, argc, argv + , "authenticate", FILTER_RUN1); +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + return need_a_filter(pamh, flags, argc, argv, "setcred", FILTER_RUN2); +} + +/* --------------- account management ---------------- */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return need_a_filter(pamh, flags, argc, argv + , "setcred", FILTER_RUN1|FILTER_RUN2 ); +} + +/* --------------- session management ---------------- */ + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + return need_a_filter(pamh, flags, argc, argv + , "open_session", FILTER_RUN1); +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + return need_a_filter(pamh, flags, argc, argv + , "close_session", FILTER_RUN2); +} + +/* --------- updating authentication tokens --------- */ + + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + int runN; + + if (flags & PAM_PRELIM_CHECK) + runN = FILTER_RUN1; + else if (flags & PAM_UPDATE_AUTHTOK) + runN = FILTER_RUN2; + else { + _pam_log(LOG_ERR, "unknown flags for chauthtok (0x%X)", flags); + return PAM_TRY_AGAIN; + } + + return need_a_filter(pamh, flags, argc, argv, "chauthtok", runN); +} + +#ifdef PAM_STATIC + +/* ------------ stuff for static modules ------------ */ + +struct pam_module _pam_filter_modstruct = { + "pam_filter", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok, +}; + +#endif diff --git a/modules/pam_filter/upperLOWER/.cvsignore b/modules/pam_filter/upperLOWER/.cvsignore new file mode 100644 index 00000000..bcd63650 --- /dev/null +++ b/modules/pam_filter/upperLOWER/.cvsignore @@ -0,0 +1 @@ +upperLOWER diff --git a/modules/pam_filter/upperLOWER/Makefile b/modules/pam_filter/upperLOWER/Makefile new file mode 100644 index 00000000..a75b06bb --- /dev/null +++ b/modules/pam_filter/upperLOWER/Makefile @@ -0,0 +1,64 @@ +# +# $Id$ +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:37 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.5 1997/04/05 06:41:35 morgan +# fakeroot +# +# Revision 1.4 1997/01/04 20:25:04 morgan +# removed need for make +# +# Revision 1.3 1996/11/10 20:13:08 morgan +# email address +# +# Revision 1.2 1996/11/10 20:12:24 morgan +# cross platform support +# +# Revision 1.1 1996/06/02 08:17:02 morgan +# Initial revision +# +# +# This directory contains a pam_filter filter executable +# +# Created by Andrew Morgan 1996/3/11 +# + +TITLE=upperLOWER + +# + +OBJS = $(TITLE).o + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + +all: $(TITLE) + +$(TITLE): $(OBJS) + $(CC) -o $(TITLE) $(OBJS) + strip $(TITLE) + +install: + $(MKDIR) $(FAKEROOT)$(FILTERSDIR) + $(INSTALL) -m 511 $(TITLE) $(FAKEROOT)$(FILTERSDIR) + +remove: + cd $(FAKEROOT)$(FILTERSDIR) && rm -f $(TITLE) + +clean: + rm -f $(TITLE) $(OBJS) core *~ + +extraclean: clean + rm -f *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_filter/upperLOWER/upperLOWER.c b/modules/pam_filter/upperLOWER/upperLOWER.c new file mode 100644 index 00000000..3f7d26cd --- /dev/null +++ b/modules/pam_filter/upperLOWER/upperLOWER.c @@ -0,0 +1,174 @@ +/* + * $Id$ + * + * This is a sample filter program, for use with pam_filter (a module + * provided with Linux-PAM). This filter simply transposes upper and + * lower case letters, it is intended for demonstration purposes and + * it serves no purpose other than to annoy the user... + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:37 agmorgan + * Initial revision + * + * Revision 1.2 1999/07/08 05:01:48 morgan + * glibc fixes (Thorsten Kukuk, Adam J. Richter) + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.1 1996/06/02 08:17:02 morgan + * Initial revision + * + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include + +#include + +/* ---------------------------------------------------------------- */ + +#include +#ifdef hpux +# define log_this syslog +#else +static void log_this(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("upperLOWER", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} +#endif + +#include + +static void do_transpose(char *buffer,int len) +{ + int i; + for (i=0; i %s\r\n",envp[i]); + } + fprintf(stderr,"]: end\r\n"); + } +#endif + + if (argc != 1) { +#ifdef DEBUG + fprintf(stderr,"filter invoked as conventional executable\n"); +#else + log_this(LOG_ERR, "filter invoked as conventional executable"); +#endif + exit(1); + } + + before_user = before_app = do_transpose; /* assign filter functions */ + + /* enter a loop that deals with the input and output of the + user.. passing it to and from the application */ + + FD_ZERO(&readers); /* initialize reading mask */ + + for (;;) { + + FD_SET(APPOUT_FILENO, &readers); /* wake for output */ + FD_SET(APPERR_FILENO, &readers); /* wake for error */ + FD_SET(STDIN_FILENO, &readers); /* wake for input */ + + if ( select(APPTOP_FILE,&readers,NULL,NULL,NULL) < 0 ) { +#ifdef DEBUG + fprintf(stderr,"select failed\n"); +#else + log_this(LOG_WARNING,"select failed"); +#endif + break; + } + + /* application errors */ + + if ( FD_ISSET(APPERR_FILENO,&readers) ) { + int got = read(APPERR_FILENO, buffer, BUFSIZ); + if (got <= 0) { + break; + } else { + /* translate to give to real terminal */ + if (before_user != NULL) + before_user(buffer, got); + if ( write(STDERR_FILENO, buffer, got) != got ) { + log_this(LOG_WARNING,"couldn't write %d bytes?!",got); + break; + } + } + } else if ( FD_ISSET(APPOUT_FILENO,&readers) ) { /* app output */ + int got = read(APPOUT_FILENO, buffer, BUFSIZ); + if (got <= 0) { + break; + } else { + /* translate to give to real terminal */ + if (before_user != NULL) + before_user(buffer, got); + if ( write(STDOUT_FILENO, buffer, got) != got ) { + log_this(LOG_WARNING,"couldn't write %d bytes!?",got); + break; + } + } + } + + if ( FD_ISSET(STDIN_FILENO, &readers) ) { /* user input */ + int got = read(STDIN_FILENO, buffer, BUFSIZ); + if (got < 0) { + log_this(LOG_WARNING,"user input junked"); + break; + } else if (got) { + /* translate to give to application */ + if (before_app != NULL) + before_app(buffer, got); + if ( write(APPIN_FILENO, buffer, got) != got ) { + log_this(LOG_WARNING,"couldn't pass %d bytes!?",got); + break; + } + } else { + /* nothing received -- an error? */ + log_this(LOG_WARNING,"user input null?"); + break; + } + } + } + + exit(0); +} + + + diff --git a/modules/pam_ftp/.cvsignore b/modules/pam_ftp/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_ftp/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_ftp/Makefile b/modules/pam_ftp/Makefile new file mode 100644 index 00000000..0ce77a7a --- /dev/null +++ b/modules/pam_ftp/Makefile @@ -0,0 +1,88 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/11/14 +# + +TITLE=pam_ftp + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_ftp/README b/modules/pam_ftp/README new file mode 100644 index 00000000..6d03330c --- /dev/null +++ b/modules/pam_ftp/README @@ -0,0 +1,18 @@ +This is the README for pam_ftp +------------------------------ + +This module is an authentication module that does simple ftp +authentication. + +Recognized arguments: + + "debug" print debug messages + "users=" comma separated list of users which + could login only with email adress + "ignore" allow invalid email adresses + +Options for: +auth: for authentication it provides pam_authenticate() and + pam_setcred() hooks. + +Thorsten Kukuk , 17. June 1999 diff --git a/modules/pam_ftp/pam_ftp.c b/modules/pam_ftp/pam_ftp.c new file mode 100644 index 00000000..eb2c3fd9 --- /dev/null +++ b/modules/pam_ftp/pam_ftp.c @@ -0,0 +1,299 @@ +/* pam_ftp module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + * + */ + +#define PLEASE_ENTER_PASSWORD "Password required for %s." +#define GUEST_LOGIN_PROMPT "Guest login ok, " \ +"send your complete e-mail address as password." + +/* the following is a password that "can't be correct" */ +#define BLOCK_PASSWORD "\177BAD PASSWPRD\177" + +#define _GNU_SOURCE +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-ftp", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static int converse(pam_handle_t *pamh, int nargs + , struct pam_message **message + , struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse\n")); + + retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; + if ( retval == PAM_SUCCESS ) { + + retval = conv->conv(nargs, ( const struct pam_message ** ) message + , response, conv->appdata_ptr); + + D(("returned from application's conversation function\n")); + + if ((retval != PAM_SUCCESS) && (retval != PAM_CONV_AGAIN)) { + _pam_log(LOG_DEBUG, "conversation failure [%s]" + , pam_strerror(pamh, retval)); + } + + } else { + _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]" + , pam_strerror(pamh, retval)); + } + + D(("ready to return from module conversation\n")); + + return retval; /* propagate error status */ +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 01 +#define PAM_IGNORE_EMAIL 02 +#define PAM_NO_ANON 04 + +static int _pam_parse(int argc, const char **argv, char **users) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strncmp(*argv,"users=",6)) { + *users = x_strdup(6+*argv); + if (*users == NULL) { + ctrl |= PAM_NO_ANON; + _pam_log(LOG_CRIT, "failed to duplicate user list - anon off"); + } + } else if (!strcmp(*argv,"ignore")) { + ctrl |= PAM_IGNORE_EMAIL; + } else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* + * check if name is in list or default list. place users name in *_user + * return 1 if listed 0 if not. + */ + +static int lookup(const char *name, char *list, const char **_user) +{ + int anon = 0; + + *_user = name; /* this is the default */ + if (list) { + const char *l; + char *x; + + x = list; + while ((l = strtok(x, ","))) { + x = NULL; + if (!strcmp(name, l)) { + *_user = list; + anon = 1; + } + } + } else { +#define MAX_L 2 + static const char *l[MAX_L] = { "ftp", "anonymous" }; + int i; + + for (i=0; iresp, "@"); + retval = pam_set_item(pamh, PAM_RUSER, token); + + if ((token) && (retval == PAM_SUCCESS)) { + token = strtok(NULL, "@"); + retval = pam_set_item(pamh, PAM_RHOST, token); + } + } + + /* we are happy to grant annonymous access to the user */ + retval = PAM_SUCCESS; + + } else { + /* + * we have a password so set AUTHTOK + */ + + (void) pam_set_item(pamh, PAM_AUTHTOK, resp->resp); + + /* + * this module failed, but the next one might succeed with + * this password. + */ + + retval = PAM_AUTH_ERR; + } + + if (resp) { /* clean up */ + _pam_drop_reply(resp, i); + } + + /* success or failure */ + + return retval; + } +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_IGNORE; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_ftp_modstruct = { + "pam_ftp", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_group/.cvsignore b/modules/pam_group/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_group/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_group/Makefile b/modules/pam_group/Makefile new file mode 100644 index 00000000..b61a57de --- /dev/null +++ b/modules/pam_group/Makefile @@ -0,0 +1,98 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/6/11 +# + +TITLE=pam_group +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/group.conf +export CONFILE + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +DEFS=-DCONFILE=\"$(CONFILE)\" + +CFLAGS += $(DEFS) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(ELIBS) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(ELIBS) +endif + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SCONFIGED) + bash -f ./install_conf + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(CONFILE) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + rm -f ./.ignore_age + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_group/group.conf b/modules/pam_group/group.conf new file mode 100644 index 00000000..bdd76adb --- /dev/null +++ b/modules/pam_group/group.conf @@ -0,0 +1,60 @@ +## +## Note, to get this to work as it is currently typed you need +## +## 1. to run an application as root +## 2. add the following groups to the /etc/group file: +## floppy, games, sound +## +# +# *** Please note that giving group membership on a session basis is +# *** NOT inherently secure. If a user can create an executable that +# *** is setgid a group that they are infrequently given membership +# *** of, they can basically obtain group membership any time they +# *** like. Example: games are alowed between the hours of 6pm and 6am +# *** user joe logs in at 7pm writes a small C-program toplay.c that +# *** invokes their favorite shell, compiles it and does +# *** "chgrp games toplay; chmod g+s toplay". They are basically able +# *** to play games any time... You have been warned. AGM +# +# this is an example configuration file for the pam_group module. Its +# syntax is based on that of the pam_time module and (at some point in +# the distant past was inspired by the 'shadow' package) +# +# the syntax of the lines is as follows: +# +# services;ttys;users;times;groups +# +# white space is ignored and lines maybe extended with '\\n' (escaped +# newlines). From reading these comments, it is clear that +# text following a '#' is ignored to the end of the line. +# +# the first four fields are described in the pam_time directory. +# The only difference for these is how the time field is interpretted: +# it is used to indicate "when" these groups are to be given to the user. +# +# groups +# The (comma or space separated) list of groups that the user +# inherits membership of. These groups are added if the previous +# fields are satisfied by the user's request +# + +# +# Here is a simple example: running 'xsh' on tty* (any ttyXXX device), +# the user 'us' is given access to the floppy (through membership of +# the floppy group) +# + +#xsh;tty*&!ttyp*;us;Al0000-2400;floppy + +# +# another example: running 'xsh' on tty* (any ttyXXX device), +# the user 'sword' is given access to games (through membership of +# the floppy group) after work hours +# + +#xsh; tty* ;sword;!Wk0900-1800;games, sound +#xsh; tty* ;*;Al0900-1800;floppy + +# +# End of group.conf file +# diff --git a/modules/pam_group/install_conf b/modules/pam_group/install_conf new file mode 100755 index 00000000..03bb7edb --- /dev/null +++ b/modules/pam_group/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +QUIET_INSTALL=../../.quiet_install +CONF=./group.conf +MODULE=pam_group + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_group/pam_group.c b/modules/pam_group/pam_group.c new file mode 100644 index 00000000..81953f76 --- /dev/null +++ b/modules/pam_group/pam_group.c @@ -0,0 +1,843 @@ +/* pam_group module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/7/6 + */ + +const static char rcsid[] = +"$Id$;\n" +"Version 0.5 for Linux-PAM\n" +"Copyright (c) Andrew G. Morgan 1996 \n"; + +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PAM_GROUP_CONF CONFILE /* from external define */ +#define PAM_GROUP_BUFLEN 1000 +#define FIELD_SEPARATOR ';' /* this is new as of .02 */ + +typedef enum { FALSE, TRUE } boolean; +typedef enum { AND, OR } operator; + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_AUTH + +#include +#include + +/* --- static functions for checking whether the user should be let in --- */ + +static void _log_err(const char *format, ... ) +{ + va_list args; + + va_start(args, format); + openlog("pam_group", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(LOG_CRIT, format, args); + va_end(args); + closelog(); +} + +static void shift_bytes(char *mem, int from, int by) +{ + while (by-- > 0) { + *mem = mem[from]; + ++mem; + } +} + +static int read_field(int fd, char **buf, int *from, int *to) +{ + /* is buf set ? */ + + if (! *buf) { + *buf = (char *) malloc(PAM_GROUP_BUFLEN); + if (! *buf) { + _log_err("out of memory"); + return -1; + } + *from = *to = 0; + fd = open(PAM_GROUP_CONF, O_RDONLY); + } + + /* do we have a file open ? return error */ + + if (fd < 0 && *to <= 0) { + _log_err( PAM_GROUP_CONF " not opened"); + memset(*buf, 0, PAM_GROUP_BUFLEN); + _pam_drop(*buf); + return -1; + } + + /* check if there was a newline last time */ + + if ((*to > *from) && (*to > 0) + && ((*buf)[*from] == '\0')) { /* previous line ended */ + (*from)++; + (*buf)[0] = '\0'; + return fd; + } + + /* ready for more data: first shift the buffer's remaining data */ + + *to -= *from; + shift_bytes(*buf, *from, *to); + *from = 0; + (*buf)[*to] = '\0'; + + while (fd >= 0 && *to < PAM_GROUP_BUFLEN) { + int i; + + /* now try to fill the remainder of the buffer */ + + i = read(fd, *to + *buf, PAM_GROUP_BUFLEN - *to); + if (i < 0) { + _log_err("error reading " PAM_GROUP_CONF); + return -1; + } else if (!i) { + close(fd); + fd = -1; /* end of file reached */ + } else + *to += i; + + /* + * contract the buffer. Delete any comments, and replace all + * multiple spaces with single commas + */ + + i = 0; +#ifdef DEBUG_DUMP + D(("buffer=<%s>",*buf)); +#endif + while (i < *to) { + if ((*buf)[i] == ',') { + int j; + + for (j=++i; j<*to && (*buf)[j] == ','; ++j); + if (j!=i) { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + } + } + switch ((*buf)[i]) { + int j,c; + case '#': + for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j); + if (j >= *to) { + (*buf)[*to = ++i] = '\0'; + } else if (c == '\n') { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + ++i; + } else { + _log_err("internal error in " __FILE__ + " at line %d", __LINE__ ); + return -1; + } + break; + case '\\': + if ((*buf)[i+1] == '\n') { + shift_bytes(i + *buf, 2, *to - (i+2)); + *to -= 2; + } + break; + case '!': + case ' ': + case '\t': + if ((*buf)[i] != '!') + (*buf)[i] = ','; + /* delete any trailing spaces */ + for (j=++i; j < *to && ( (c = (*buf)[j]) == ' ' + || c == '\t' ); ++j); + shift_bytes(i + *buf, j-i, (*to)-j ); + *to -= j-i; + break; + default: + ++i; + } + } + } + + (*buf)[*to] = '\0'; + + /* now return the next field (set the from/to markers) */ + { + int i; + + for (i=0; i<*to; ++i) { + switch ((*buf)[i]) { + case '#': + case '\n': /* end of the line/file */ + (*buf)[i] = '\0'; + *from = i; + return fd; + case FIELD_SEPARATOR: /* end of the field */ + (*buf)[i] = '\0'; + *from = ++i; + return fd; + } + } + *from = i; + (*buf)[*from] = '\0'; + } + + if (*to <= 0) { + D(("[end of text]")); + *buf = NULL; + } + return fd; +} + +/* read a member from a field */ + +static int logic_member(const char *string, int *at) +{ + int len,c,to; + int done=0; + int token=0; + + len=0; + to=*at; + do { + c = string[to++]; + + switch (c) { + + case '\0': + --to; + done = 1; + break; + + case '&': + case '|': + case '!': + if (token) { + --to; + } + done = 1; + break; + + default: + if (isalpha(c) || c == '*' || isdigit(c) || c == '_' + || c == '-' || c == '.') { + token = 1; + } else if (token) { + --to; + done = 1; + } else { + ++*at; + } + } + } while (!done); + + return to - *at; +} + +typedef enum { VAL, OP } expect; + +static boolean logic_field(const void *me, const char *x, int rule, + boolean (*agrees)(const void *, const char * + , int, int)) +{ + boolean left=FALSE, right, not=FALSE; + operator oper=OR; + int at=0, l; + expect next=VAL; + + while ((l = logic_member(x,&at))) { + int c = x[at]; + + if (next == VAL) { + if (c == '!') + not = !not; + else if (isalpha(c) || c == '*') { + right = not ^ agrees(me, x+at, l, rule); + if (oper == AND) + left &= right; + else + left |= right; + next = OP; + } else { + _log_err("garbled syntax; expected name (rule #%d)", rule); + return FALSE; + } + } else { /* OP */ + switch (c) { + case '&': + oper = AND; + break; + case '|': + oper = OR; + break; + default: + _log_err("garbled syntax; expected & or | (rule #%d)" + , rule); + D(("%c at %d",c,at)); + return FALSE; + } + next = VAL; + } + at += l; + } + + return left; +} + +static boolean is_same(const void *A, const char *b, int len, int rule) +{ + int i; + const char *a; + + a = A; + for (i=0; len > 0; ++i, --len) { + if (b[i] != a[i]) { + if (b[i++] == '*') { + return (!--len || !strncmp(b+i,a+strlen(a)-len,len)); + } else + return FALSE; + } + } + return ( !len ); +} + +typedef struct { + int day; /* array of 7 bits, one set for today */ + int minute; /* integer, hour*100+minute for now */ +} TIME; + +struct day { + const char *d; + int bit; +} static const days[11] = { + { "su", 01 }, + { "mo", 02 }, + { "tu", 04 }, + { "we", 010 }, + { "th", 020 }, + { "fr", 040 }, + { "sa", 0100 }, + { "wk", 076 }, + { "wd", 0101 }, + { "al", 0177 }, + { NULL, 0 } +}; + +static TIME time_now(void) +{ + struct tm *local; + time_t the_time; + TIME this; + + the_time = time((time_t *)0); /* get the current time */ + local = localtime(&the_time); + this.day = days[local->tm_wday].bit; + this.minute = local->tm_hour*100 + local->tm_min; + + D(("day: 0%o, time: %.4d", this.day, this.minute)); + return this; +} + +/* take the current date and see if the range "date" passes it */ +static boolean check_time(const void *AT, const char *times, int len, int rule) +{ + boolean not,pass; + int marked_day, time_start, time_end; + const TIME *at; + int i,j=0; + + at = AT; + D(("checking: 0%o/%.4d vs. %s", at->day, at->minute, times)); + + if (times == NULL) { + /* this should not happen */ + _log_err("internal error: " __FILE__ " line %d", __LINE__); + return FALSE; + } + + if (times[j] == '!') { + ++j; + not = TRUE; + } else { + not = FALSE; + } + + for (marked_day = 0; len > 0 && isalpha(times[j]); --len) { + int this_day=-1; + + D(("%c%c ?", times[j], times[j+1])); + for (i=0; days[i].d != NULL; ++i) { + if (tolower(times[j]) == days[i].d[0] + && tolower(times[j+1]) == days[i].d[1] ) { + this_day = days[i].bit; + break; + } + } + j += 2; + if (this_day == -1) { + _log_err("bad day specified (rule #%d)", rule); + return FALSE; + } + marked_day ^= this_day; + } + if (marked_day == 0) { + _log_err("no day specified"); + return FALSE; + } + D(("day range = 0%o", marked_day)); + + time_start = 0; + for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) { + time_start *= 10; + time_start += times[i+j]-'0'; /* is this portable? */ + } + j += i; + + if (times[j] == '-') { + time_end = 0; + for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) { + time_end *= 10; + time_end += times[i+j]-'0'; /* is this portable? */ + } + j += i; + } else + time_end = -1; + + D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j])); + if (i != 5 || time_end == -1) { + _log_err("no/bad times specified (rule #%d)", rule); + return TRUE; + } + D(("times(%d to %d)", time_start,time_end)); + D(("marked_day = 0%o", marked_day)); + + /* compare with the actual time now */ + + pass = FALSE; + if (time_start < time_end) { /* start < end ? --> same day */ + if ((at->day & marked_day) && (at->minute >= time_start) + && (at->minute < time_end)) { + D(("time is listed")); + pass = TRUE; + } + } else { /* spans two days */ + if ((at->day & marked_day) && (at->minute >= time_start)) { + D(("caught on first day")); + pass = TRUE; + } else { + marked_day <<= 1; + marked_day |= (marked_day & 0200) ? 1:0; + D(("next day = 0%o", marked_day)); + if ((at->day & marked_day) && (at->minute <= time_end)) { + D(("caught on second day")); + pass = TRUE; + } + } + } + + return (not ^ pass); +} + +static int find_member(const char *string, int *at) +{ + int len,c,to; + int done=0; + int token=0; + + len=0; + to=*at; + do { + c = string[to++]; + + switch (c) { + + case '\0': + --to; + done = 1; + break; + + case '&': + case '|': + case '!': + if (token) { + --to; + } + done = 1; + break; + + default: + if (isalpha(c) || isdigit(c) || c == '_' || c == '*' + || c == '-') { + token = 1; + } else if (token) { + --to; + done = 1; + } else { + ++*at; + } + } + } while (!done); + + return to - *at; +} + +#define GROUP_BLK 10 +#define blk_size(len) (((len-1 + GROUP_BLK)/GROUP_BLK)*GROUP_BLK) + +static int mkgrplist(char *buf, gid_t **list, int len) +{ + int l,at=0; + int blks; + + blks = blk_size(len); + D(("cf. blks=%d and len=%d", blks,len)); + + while ((l = find_member(buf,&at))) { + int edge; + + if (len >= blks) { + gid_t *tmp; + + D(("allocating new block")); + tmp = (gid_t *) realloc((*list) + , sizeof(gid_t) * (blks += GROUP_BLK)); + if (tmp != NULL) { + (*list) = tmp; + } else { + _log_err("out of memory for group list"); + free(*list); + (*list) = NULL; + return -1; + } + } + + /* '\0' terminate the entry */ + + edge = (buf[at+l]) ? 1:0; + buf[at+l] = '\0'; + D(("found group: %s",buf+at)); + + /* this is where we convert a group name to a gid_t */ +#ifdef WANT_PWDB + { + int retval; + const struct pwdb *pw=NULL; + + retval = pwdb_locate("group", PWDB_DEFAULT, buf+at + , PWDB_ID_UNKNOWN, &pw); + if (retval != PWDB_SUCCESS) { + _log_err("bad group: %s; %s", buf+at, pwdb_strerror(retval)); + } else { + const struct pwdb_entry *pwe=NULL; + + D(("group %s exists", buf+at)); + retval = pwdb_get_entry(pw, "gid", &pwe); + if (retval == PWDB_SUCCESS) { + D(("gid = %d [%p]",* (const gid_t *) pwe->value,list)); + (*list)[len++] = * (const gid_t *) pwe->value; + pwdb_entry_delete(&pwe); /* tidy up */ + } else { + _log_err("%s group entry is bad; %s" + , pwdb_strerror(retval)); + } + pw = NULL; /* break link - cached for later use */ + } + } +#else + { + const struct group *grp; + + grp = getgrnam(buf+at); + if (grp == NULL) { + _log_err("bad group: %s", buf+at); + } else { + D(("group %s exists", buf+at)); + (*list)[len++] = grp->gr_gid; + } + } +#endif + + /* next entry along */ + + at += l + edge; + } + D(("returning with [%p/len=%d]->%p",list,len,*list)); + return len; +} + + +static int check_account(const char *service, const char *tty + , const char *user) +{ + int from=0,to=0,fd=-1; + char *buffer=NULL; + int count=0; + TIME here_and_now; + int retval=PAM_SUCCESS; + gid_t *grps; + int no_grps; + + /* + * first we get the current list of groups - the application + * will have previously done an initgroups(), or equivalent. + */ + + D(("counting supplementary groups")); + no_grps = getgroups(0, NULL); /* find the current number of groups */ + if (no_grps > 0) { + grps = calloc( blk_size(no_grps) , sizeof(gid_t) ); + D(("copying current list into grps [%d big]",blk_size(no_grps))); + (void) getgroups(no_grps, grps); +#ifdef DEBUG + { + int z; + for (z=0; z 0) { + D(("rule #%d passed, added %d groups", count, good)); + } else if (good < 0) { + retval = PAM_BUF_ERR; + } else { + D(("rule #%d failed", count)); + } + + } while (buffer); + + /* now set the groups for the user */ + + if (no_grps > 0) { + int err; + D(("trying to set %d groups", no_grps)); +#ifdef DEBUG + for (err=0; err 1996/10/24 +# + +TITLE=pam_issue + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + mkdir -p ./dynamic +endif +ifdef STATIC + mkdir -p ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(LINKLIBS) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + mkdir -p $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + install -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_issue/pam_issue.c b/modules/pam_issue/pam_issue.c new file mode 100644 index 00000000..9fbf2551 --- /dev/null +++ b/modules/pam_issue/pam_issue.c @@ -0,0 +1,265 @@ +/* pam_issue module - a simple /etc/issue parser to set PAM_USER_PROMPT + * + * Copyright 1999 by Ben Collins + * + * Needs to be called before any other auth modules so we can setup the + * user prompt before it's first used. Allows one argument option, which + * is the full path to a file to be used for issue (uses /etc/issue as a + * default) such as "issue=/etc/issue.telnet". + * + * We can also parse escapes within the the issue file (enabled by + * default, but can be disabled with the "noesc" option). It's the exact + * same parsing as util-linux's agetty program performs. + * + * Released under the GNU LGPL version 2 or later + */ + +#define _GNU_SOURCE +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PAM_SM_AUTH + +#include + +static int _user_prompt_set = 0; + +char *do_prompt (FILE *); + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + int retval = PAM_SUCCESS; + FILE *fd; + int parse_esc = 1; + char *prompt_tmp = NULL, *cur_prompt = NULL; + struct stat st; + char *issue_file = NULL; + + /* If we've already set the prompt, don't set it again */ + if(_user_prompt_set) + return PAM_IGNORE; + else + /* we set this here so if we fail below, we wont get further + than this next time around (only one real failure) */ + _user_prompt_set = 1; + + for ( ; argc-- > 0 ; ++argv ) { + if (!strncmp(*argv,"issue=",6)) { + issue_file = (char *) strdup(6+*argv); + if (issue_file != NULL) { + D(("set issue_file to: %s", issue_file)); + } else { + D(("failed to strdup issue_file - ignored")); + return PAM_IGNORE; + } + } else if (!strcmp(*argv,"noesc")) { + parse_esc = 0; + D(("turning off escape parsing by request")); + } else + D(("unknown option passed: %s", *argv)); + } + + if (issue_file == NULL) + issue_file = strdup("/etc/issue"); + + if ((fd = fopen(issue_file, "r")) != NULL) { + int tot_size = 0; + + if (stat(issue_file, &st) < 0) + return PAM_IGNORE; + + retval = pam_get_item(pamh, PAM_USER_PROMPT, (const void **) &cur_prompt); + if (retval != PAM_SUCCESS) + return PAM_IGNORE; + + /* first read in the issue file */ + + if (parse_esc) + prompt_tmp = do_prompt(fd); + else { + int count = 0; + prompt_tmp = malloc(st.st_size + 1); + if (prompt_tmp == NULL) return PAM_IGNORE; + memset (prompt_tmp, '\0', st.st_size + 1); + count = fread(prompt_tmp, sizeof(char *), st.st_size, fd); + prompt_tmp[st.st_size] = '\0'; + } + + fclose(fd); + + tot_size = strlen(prompt_tmp) + strlen(cur_prompt) + 1; + + /* + * alloc some extra space for the original prompt + * and postpend it to the buffer + */ + prompt_tmp = realloc(prompt_tmp, tot_size); + strcpy(prompt_tmp+strlen(prompt_tmp), cur_prompt); + + prompt_tmp[tot_size] = '\0'; + + retval = pam_set_item(pamh, PAM_USER_PROMPT, (const char *) prompt_tmp); + + free(issue_file); + free(prompt_tmp); + } else { + D(("could not open issue_file: %s", issue_file)); + return PAM_IGNORE; + } + + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +char *do_prompt(FILE *fd) +{ + int c, size = 1024; + char *issue = (char *)malloc(size); + char buf[1024]; + struct utsname uts; + + if (issue == NULL || fd == NULL) + return NULL; + + issue[0] = '\0'; /* zero this, for strcat to work on first buf */ + (void) uname(&uts); + + while ((c = getc(fd)) != EOF) { + if (c == '\\') { + c = getc(fd); + switch (c) { + case 's': + snprintf (buf, 1024, "%s", uts.sysname); + break; + case 'n': + snprintf (buf, 1024, "%s", uts.nodename); + break; + case 'r': + snprintf (buf, 1024, "%s", uts.release); + break; + case 'v': + snprintf (buf, 1024, "%s", uts.version); + break; + case 'm': + snprintf (buf, 1024, "%s", uts.machine); + break; + case 'o': + { + char domainname[256]; + + getdomainname(domainname, sizeof(domainname)); + domainname[sizeof(domainname)-1] = '\0'; + snprintf (buf, 1024, "%s", domainname); + } + break; + + case 'd': + case 't': + { + const char *weekday[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", + "Fri", "Sat" }; + const char *month[] = { + "Jan", "Feb", "Mar", "Apr", "May", + "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec" }; + time_t now; + struct tm *tm; + + (void) time (&now); + tm = localtime(&now); + + if (c == 'd') + snprintf (buf, 1024, "%s %s %d %d", + weekday[tm->tm_wday], month[tm->tm_mon], + tm->tm_mday, + tm->tm_year + 1900); + else + snprintf (buf, 1024, "%02d:%02d:%02d", + tm->tm_hour, tm->tm_min, tm->tm_sec); + } + break; + case 'l': + { + char *ttyn = ttyname(1); + if (!strncmp(ttyn, "/dev/", 5)) + ttyn += 5; + snprintf (buf, 1024, "%s", ttyn); + } + break; + case 'u': + case 'U': + { + int users = 0; + struct utmp *ut; + setutent(); + while ((ut = getutent())) + if (ut->ut_type == USER_PROCESS) + users++; + endutent(); + printf ("%d ", users); + if (c == 'U') + snprintf (buf, 1024, "%s", (users == 1) ? + " user" : " users"); + break; + } + default: + buf[0] = c; buf[1] = '\0'; + } + if ((strlen(issue) + strlen(buf)) < size + 1) { + size += strlen(buf) + 1; + issue = (char *) realloc (issue, size); + } + strcat(issue, buf); + } else { + buf[0] = c; buf[1] = '\0'; + if ((strlen(issue) + strlen(buf)) < size + 1) { + size += strlen(buf) + 1; + issue = (char *) realloc (issue, size); + } + strcat(issue, buf); + } + } + return issue; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_issue_modstruct = { + "pam_issue", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_lastlog/.cvsignore b/modules/pam_lastlog/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_lastlog/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_lastlog/Makefile b/modules/pam_lastlog/Makefile new file mode 100644 index 00000000..b5a6f212 --- /dev/null +++ b/modules/pam_lastlog/Makefile @@ -0,0 +1,112 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:40 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:17 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.2 1997/04/05 06:17:14 morgan +# fakeroot fixed +# +# Revision 1.1 1997/01/04 20:29:28 morgan +# Initial revision +# +# +# +# Created by Andrew Morgan 1996/12/8 +# + +# Convenient defaults for compiling independently of the full source +# tree. +ifndef FULL_LINUX_PAM_SOURCE_TREE +export DYNAMIC=-DPAM_DYNAMIC +export CC=gcc +export CFLAGS=-O2 -Dlinux -DLINUX_PAM \ + -ansi -D_POSIX_SOURCE -Wall -Wwrite-strings \ + -Wpointer-arith -Wcast-qual -Wcast-align -Wtraditional \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Winline \ + -Wshadow -pedantic -fPIC +export MKDIR=mkdir -p +export LD_D=gcc -shared -Xlinker -x +endif + +TITLE=pam_lastlog + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + + +####################### don't edit below ####################### + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_lastlog/pam_lastlog.c b/modules/pam_lastlog/pam_lastlog.c new file mode 100644 index 00000000..7207976e --- /dev/null +++ b/modules/pam_lastlog/pam_lastlog.c @@ -0,0 +1,463 @@ +/* pam_lastlog module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + * + * This module does the necessary work to display the last login + * time+date for this user, it then updates this entry for the + * present (login) service. + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#ifdef HAVE_UTMP_H +# include +#else +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WANT_PWDB +#include /* use POSIX front end */ +#endif + +#if defined(hpux) || defined(sunos) || defined(solaris) +# ifndef _PATH_LASTLOG +# define _PATH_LASTLOG "/usr/adm/lastlog" +# endif /* _PATH_LASTLOG */ +# ifndef UT_HOSTSIZE +# define UT_HOSTSIZE 16 +# endif /* UT_HOSTSIZE */ +# ifndef UT_LINESIZE +# define UT_LINESIZE 12 +# endif /* UT_LINESIZE */ +#endif +#if defined(hpux) +struct lastlog { + time_t ll_time; + char ll_line[UT_LINESIZE]; + char ll_host[UT_HOSTSIZE]; /* same as in utmp */ +}; +#endif /* hpux */ + +/* XXX - time before ignoring lock. Is 1 sec enough? */ +#define LASTLOG_IGNORE_LOCK_TIME 1 + +#define DEFAULT_HOST "" /* "[no.where]" */ +#define DEFAULT_TERM "" /* "tt???" */ +#define LASTLOG_NEVER_WELCOME "Welcome to your new account!" +#define LASTLOG_INTRO "Last login:" +#define LASTLOG_TIME " %s" +#define _LASTLOG_HOST_FORMAT " from %%.%ds" +#define _LASTLOG_LINE_FORMAT " on %%.%ds" +#define LASTLOG_TAIL "" +#define LASTLOG_MAXSIZE (sizeof(LASTLOG_INTRO)+0 \ + +sizeof(LASTLOG_TIME)+strlen(the_time) \ + +sizeof(_LASTLOG_HOST_FORMAT)+UT_HOSTSIZE \ + +sizeof(_LASTLOG_LINE_FORMAT)+UT_LINESIZE \ + +sizeof(LASTLOG_TAIL)) + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_SESSION + +#include +#include + +/* some syslogging */ + +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-lastlog", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ + +#define LASTLOG_DATE 01 /* display the date of the last login */ +#define LASTLOG_HOST 02 /* display the last host used (if set) */ +#define LASTLOG_LINE 04 /* display the last terminal used */ +#define LASTLOG_NEVER 010 /* display a welcome message for first login */ +#define LASTLOG_DEBUG 020 /* send info to syslog(3) */ +#define LASTLOG_QUIET 040 /* keep quiet about things */ + +static int _pam_parse(int flags, int argc, const char **argv) +{ + int ctrl=(LASTLOG_DATE|LASTLOG_HOST|LASTLOG_LINE); + + /* does the appliction require quiet? */ + if (flags & PAM_SILENT) { + ctrl |= LASTLOG_QUIET; + } + + /* step through arguments */ + for (; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) { + ctrl |= LASTLOG_DEBUG; + } else if (!strcmp(*argv,"nodate")) { + ctrl |= ~LASTLOG_DATE; + } else if (!strcmp(*argv,"noterm")) { + ctrl |= ~LASTLOG_LINE; + } else if (!strcmp(*argv,"nohost")) { + ctrl |= ~LASTLOG_HOST; + } else if (!strcmp(*argv,"silent")) { + ctrl |= LASTLOG_QUIET; + } else if (!strcmp(*argv,"never")) { + ctrl |= LASTLOG_NEVER; + } else { + _log_err(LOG_ERR,"unknown option; %s",*argv); + } + } + + D(("ctrl = %o", ctrl)); + return ctrl; +} + +/* a front end for conversations */ + +static int converse(pam_handle_t *pamh, int ctrl, int nargs + , struct pam_message **message + , struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; + if ( retval == PAM_SUCCESS ) { + + retval = conv->conv(nargs, ( const struct pam_message ** ) message + , response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && (ctrl & LASTLOG_DEBUG) ) { + _log_err(LOG_DEBUG, "conversation failure [%s]" + , pam_strerror(pamh, retval)); + } + + } else { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + , pam_strerror(pamh, retval)); + } + + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +static int make_remark(pam_handle_t *pamh, int ctrl, const char *remark) +{ + int retval; + + if (!(ctrl & LASTLOG_QUIET)) { + struct pam_message msg[1], *mesg[1]; + struct pam_response *resp=NULL; + + mesg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = remark; + + retval = converse(pamh, ctrl, 1, mesg, &resp); + + msg[0].msg = NULL; + if (resp) { + _pam_drop_reply(resp, 1); + } + } else { + D(("keeping quiet")); + retval = PAM_SUCCESS; + } + + D(("returning %s", pam_strerror(pamh, retval))); + return retval; +} + +/* + * Values for the announce flags.. + */ + +static int last_login_date(pam_handle_t *pamh, int announce, uid_t uid) +{ + struct flock last_lock; + struct lastlog last_login; + int retval = PAM_SESSION_ERR; + int last_fd; + + /* obtain the last login date and all the relevant info */ + last_fd = open(_PATH_LASTLOG, O_RDWR); + if (last_fd < 0) { + D(("unable to open the %s file", _PATH_LASTLOG)); + if (announce & LASTLOG_DEBUG) { + _log_err(LOG_DEBUG, "unable to open %s file", _PATH_LASTLOG); + } + retval = PAM_PERM_DENIED; + } else { + int win; + + /* read the lastlogin file - for this uid */ + (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET); + + memset(&last_lock, 0, sizeof(last_lock)); + last_lock.l_type = F_RDLCK; + last_lock.l_whence = SEEK_SET; + last_lock.l_start = sizeof(last_login) * (off_t) uid; + last_lock.l_len = sizeof(last_login); + + if ( fcntl(last_fd, F_SETLK, &last_lock) < 0 ) { + D(("locking %s failed..(waiting a little)", _PATH_LASTLOG)); + _log_err(LOG_ALERT, "%s file is locked/read", _PATH_LASTLOG); + sleep(LASTLOG_IGNORE_LOCK_TIME); + } + + win = ( read(last_fd, &last_login, sizeof(last_login)) + == sizeof(last_login) ); + + last_lock.l_type = F_UNLCK; + (void) fcntl(last_fd, F_SETLK, &last_lock); /* unlock */ + + if (!win) { + D(("First login for user uid=%d", _PATH_LASTLOG, uid)); + if (announce & LASTLOG_DEBUG) { + _log_err(LOG_DEBUG, "creating lastlog for uid %d", uid); + } + memset(&last_login, 0, sizeof(last_login)); + } + + /* rewind */ + (void) lseek(last_fd, sizeof(last_login) * (off_t) uid, SEEK_SET); + + if (!(announce & LASTLOG_QUIET)) { + if (last_login.ll_time) { + char *the_time; + char *remark; + + the_time = ctime(&last_login.ll_time); + the_time[-1+strlen(the_time)] = '\0'; /* delete '\n' */ + + remark = malloc(LASTLOG_MAXSIZE); + if (remark == NULL) { + D(("no memory for last login remark")); + retval = PAM_BUF_ERR; + } else { + int at; + + /* printing prefix */ + at = sprintf(remark, "%s", LASTLOG_INTRO); + + /* we want the date? */ + if (announce & LASTLOG_DATE) { + at += sprintf(remark+at, LASTLOG_TIME, the_time); + } + + /* we want & have the host? */ + if ((announce & LASTLOG_HOST) + && (last_login.ll_host[0] != '\0')) { + char format[2*sizeof(_LASTLOG_HOST_FORMAT)]; + + (void) sprintf(format, _LASTLOG_HOST_FORMAT + , UT_HOSTSIZE); + D(("format: %s", format)); + at += sprintf(remark+at, format, last_login.ll_host); + _pam_overwrite(format); + } + + /* we want and have the terminal? */ + if ((announce & LASTLOG_LINE) + && (last_login.ll_line[0] != '\0')) { + char format[2*sizeof(_LASTLOG_LINE_FORMAT)]; + + (void) sprintf(format, _LASTLOG_LINE_FORMAT + , UT_LINESIZE); + D(("format: %s", format)); + at += sprintf(remark+at, format, last_login.ll_line); + _pam_overwrite(format); + } + + /* display requested combo */ + sprintf(remark+at, "%s", LASTLOG_TAIL); + + retval = make_remark(pamh, announce, remark); + + /* free all the stuff malloced */ + _pam_overwrite(remark); + _pam_drop(remark); + } + } else if ((!last_login.ll_time) && (announce & LASTLOG_NEVER)) { + D(("this is the first time this user has logged in")); + retval = make_remark(pamh, announce, LASTLOG_NEVER_WELCOME); + } + } else { + D(("no text was requested")); + retval = PAM_SUCCESS; + } + + /* write latest value */ + { + const char *remote_host=NULL + , *terminal_line=DEFAULT_TERM; + + /* set this login date */ + D(("set the most recent login time")); + + (void) time(&last_login.ll_time); /* set the time */ + + /* set the remote host */ + (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host); + if (remote_host == NULL) { + remote_host = DEFAULT_HOST; + } + + /* copy to last_login */ + strncpy(last_login.ll_host, remote_host + , sizeof(last_login.ll_host)); + remote_host = NULL; + + /* set the terminal line */ + (void) pam_get_item(pamh, PAM_TTY, (const void **)&terminal_line); + D(("terminal = %s", terminal_line)); + if (terminal_line == NULL) { + terminal_line = DEFAULT_TERM; + } else if ( !strncmp("/dev/", terminal_line, 5) ) { + /* strip leading "/dev/" from tty.. */ + terminal_line += 5; + } + D(("terminal = %s", terminal_line)); + + /* copy to last_login */ + strncpy(last_login.ll_line, terminal_line + , sizeof(last_login.ll_line)); + terminal_line = NULL; + + D(("locking last_log file")); + + /* now we try to lock this file-record exclusively; non-blocking */ + memset(&last_lock, 0, sizeof(last_lock)); + last_lock.l_type = F_WRLCK; + last_lock.l_whence = SEEK_SET; + last_lock.l_start = sizeof(last_login) * (off_t) uid; + last_lock.l_len = sizeof(last_login); + + if ( fcntl(last_fd, F_SETLK, &last_lock) < 0 ) { + D(("locking %s failed..(waiting a little)", _PATH_LASTLOG)); + _log_err(LOG_ALERT, "%s file is locked/write", _PATH_LASTLOG); + sleep(LASTLOG_IGNORE_LOCK_TIME); + } + + D(("writing to the last_log file")); + (void) write(last_fd, &last_login, sizeof(last_login)); + + last_lock.l_type = F_UNLCK; + (void) fcntl(last_fd, F_SETLK, &last_lock); /* unlock */ + D(("unlocked")); + + close(last_fd); /* all done */ + } + D(("all done with last login")); + } + + /* reset the last login structure */ + memset(&last_login, 0, sizeof(last_login)); + + return retval; +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc + , const char **argv) +{ + int retval, ctrl; + const char *user; + const struct passwd *pwd; + uid_t uid; + + /* + * this module gets the uid of the PAM_USER. Uses it to display + * last login info and then updates the lastlog for that user. + */ + + ctrl = _pam_parse(flags, argc, argv); + + /* which user? */ + + retval = pam_get_item(pamh, PAM_USER, (const void **)&user); + if (retval != PAM_SUCCESS || user == NULL || *user == '\0') { + _log_err(LOG_NOTICE, "user unknown"); + return PAM_USER_UNKNOWN; + } + + /* what uid? */ + + pwd = getpwnam(user); + if (pwd == NULL) { + D(("couldn't identify user %s", user)); + return PAM_CRED_INSUFFICIENT; + } + uid = pwd->pw_uid; + pwd = NULL; /* tidy up */ + + /* process the current login attempt (indicate last) */ + + retval = last_login_date(pamh, ctrl, uid); + + /* indicate success or failure */ + + uid = -1; /* forget this */ + + return retval; +} + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_lastlog_modstruct = { + "pam_lastlog", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_limits/.cvsignore b/modules/pam_limits/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_limits/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_limits/Makefile b/modules/pam_limits/Makefile new file mode 100644 index 00000000..fe684863 --- /dev/null +++ b/modules/pam_limits/Makefile @@ -0,0 +1,98 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Cristian Gafton 1996/09/10 +# + +ifeq ($(OS),linux) +TITLE=pam_limits +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/limits.conf +export CONFILE + +CFLAGS+=-DLIMITS_FILE=\"$(CONFILE)\" + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SCONFIGED) + bash -f ./install_conf + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ *.so + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + +else +include ../dont_makefile +endif diff --git a/modules/pam_limits/README b/modules/pam_limits/README new file mode 100644 index 00000000..06a6857a --- /dev/null +++ b/modules/pam_limits/README @@ -0,0 +1,87 @@ + +pam_limits module: + Imposing user limits on login. + +THEORY OF OPERATION: + +First, make a root-only-readable file (/etc/limits by default or LIMITS_FILE +defined Makefile) that describes the resource limits you wish to impose. No +limits are imposed on UID 0 accounts. + +Each line describes a limit for a user in the form: + + + +Where: + can be: + - an user name + - a group name, with @group syntax + - the wildcard *, for default entry + + can have the two values: + - "soft" for enforcinf the soft limits + - "hard" for enforcing hard limits + + can be one of the following: + - core - limits the core file size (KB) + - data - max data size (KB) + - fsize - maximum filesize (KB) + - memlock - max locked-in-memory address space (KB) + - nofile - max number of open files + - rss - max resident set size (KB) + - stack - max stack size (KB) + - cpu - max CPU time (MIN) + - nproc - max number of processes + - as - address space limit + - maxlogins - max number of logins for this user + - maxsyslogins - max number of logins on the system + +To completely disable limits for a user (or a group), a single dash (-) +will do (Example: 'bin -', '@admin -'). Please remember that individual +limits have priority over group limits, so if you impose no limits for admin +group, but one of the members in this group have a limits line, the user +will have its limits set according to this line. + +Also, please note that all limit settings are set PER LOGIN. They are +not global, nor are they permanent (the session only) + +In the LIMITS_FILE, the # character introduces a comment - the rest of the +line is ignored. + +The pam_limits module does its best to report configuration problems found +in LIMITS_FILE via syslog. + +EXAMPLE configuration file: +=========================== +* soft core 0 +* hard rss 10000 +@student hard nproc 20 +@faculty soft nproc 20 +@faculty hard nproc 50 +ftp hard nproc 0 +@student - maxlogins 4 + + +ARGUMENTS RECOGNIZED: + debug verbose logging + + conf=/path/to/file the limits configuration file if different from the + one set at compile time. + +MODULE SERVICES PROVIDED: + session _open_session and _close_session (blank) + +USAGE: + For the services you need resources limits (login for example) put a + the following line in /etc/pam.conf as the last line for that + service (usually after the pam_unix session line: + + login session required /lib/security/pam_limits.so + + Replace "login" for each service you are using this module, replace + "/lib/security" path with your real modules path. + +AUTHOR: + Cristian Gafton + Thanks to Elliot Lee for his comments on + improving this module. diff --git a/modules/pam_limits/install_conf b/modules/pam_limits/install_conf new file mode 100755 index 00000000..d92c1f95 --- /dev/null +++ b/modules/pam_limits/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +QUIET_INSTALL=../../.quiet_install +CONF=./limits.skel +MODULE=pam_limits + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_limits/limits.skel b/modules/pam_limits/limits.skel new file mode 100644 index 00000000..5ddd9b4c --- /dev/null +++ b/modules/pam_limits/limits.skel @@ -0,0 +1,42 @@ +# /etc/security/limits.conf +# +#Each line describes a limit for a user in the form: +# +# +# +#Where: +# can be: +# - an user name +# - a group name, with @group syntax +# - the wildcard *, for default entry +# +# can have the two values: +# - "soft" for enforcing the soft limits +# - "hard" for enforcing hard limits +# +# can be one of the following: +# - core - limits the core file size (KB) +# - data - max data size (KB) +# - fsize - maximum filesize (KB) +# - memlock - max locked-in-memory address space (KB) +# - nofile - max number of open files +# - rss - max resident set size (KB) +# - stack - max stack size (KB) +# - cpu - max CPU time (MIN) +# - nproc - max number of processes +# - as - address space limit +# - maxlogins - max number of logins for this user +# - priority - the priority to run user process with +# +# +# + +#* soft core 0 +#* hard rss 10000 +#@student hard nproc 20 +#@faculty soft nproc 20 +#@faculty hard nproc 50 +#ftp hard nproc 0 +#@student - maxlogins 4 + +# End of file diff --git a/modules/pam_limits/pam_limits.c b/modules/pam_limits/pam_limits.c new file mode 100644 index 00000000..e832dc63 --- /dev/null +++ b/modules/pam_limits/pam_limits.c @@ -0,0 +1,635 @@ +/* + * pam_limits - impose resource limits when opening a user session + * + * 1.6 - modified for PLD (added process priority settings) + * by Marcin Korzonek ut_type != USER_PROCESS) + continue; +#endif + if (ut->UT_USER[0] == '\0') + continue; + if (!flag_numsyslogins + && strncmp(name, ut->UT_USER, sizeof(ut->UT_USER)) != 0) + continue; + if (++count >= limit) + break; + } + endutent(); + if (count >= limit) { + if (name) { + _pam_log(LOG_WARNING, "Too many logins (max %d) for %s", + limit, name); + } else { + _pam_log(LOG_WARNING, "Too many system logins (max %d)", limit); + } + return LOGIN_ERR; + } + return 0; +} + +/* checks if a user is on a list of members of the GID 0 group */ +static int is_on_list(char * const *list, const char *member) +{ + while (*list) { + if (strcmp(*list, member) == 0) + return 1; + list++; + } + return 0; +} + +/* Checks if a user is a member of a group */ +static int is_on_group(const char *user_name, const char *group_name) +{ + struct passwd *pwd; + struct group *grp, *pgrp; + char uname[LINE_LENGTH], gname[LINE_LENGTH]; + + if (!strlen(user_name)) + return 0; + if (!strlen(group_name)) + return 0; + memset(uname, 0, sizeof(uname)); + strncpy(uname, user_name, LINE_LENGTH); + memset(gname, 0, sizeof(gname)); + strncpy(gname, group_name, LINE_LENGTH); + + setpwent(); + pwd = getpwnam(uname); + endpwent(); + if (!pwd) + return 0; + + /* the info about this group */ + setgrent(); + grp = getgrnam(gname); + endgrent(); + if (!grp) + return 0; + + /* first check: is a member of the group_name group ? */ + if (is_on_list(grp->gr_mem, uname)) + return 1; + + /* next check: user primary group is group_name ? */ + setgrent(); + pgrp = getgrgid(pwd->pw_gid); + endgrent(); + if (!pgrp) + return 0; + if (!strcmp(pgrp->gr_name, gname)) + return 1; + + return 0; +} + +static int init_limits(void) +{ + int retval = PAM_SUCCESS; + + D(("called.")); + + retval |= getrlimit(RLIMIT_CPU, &limits[RLIMIT_CPU].limit); + limits[RLIMIT_CPU].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_CPU].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_FSIZE, &limits[RLIMIT_FSIZE].limit); + limits[RLIMIT_FSIZE].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_FSIZE].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_DATA, &limits[RLIMIT_DATA].limit); + limits[RLIMIT_DATA].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_DATA].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_STACK, &limits[RLIMIT_STACK].limit); + limits[RLIMIT_STACK].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_STACK].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_CORE, &limits[RLIMIT_CORE].limit); + limits[RLIMIT_CORE].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_CORE].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_RSS, &limits[RLIMIT_RSS].limit); + limits[RLIMIT_RSS].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_RSS].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_NPROC, &limits[RLIMIT_NPROC].limit); + limits[RLIMIT_NPROC].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_NPROC].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_NOFILE, &limits[RLIMIT_NOFILE].limit); + limits[RLIMIT_NOFILE].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_NOFILE].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_MEMLOCK, &limits[RLIMIT_MEMLOCK].limit); + limits[RLIMIT_MEMLOCK].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_MEMLOCK].src_hard = LIMITS_DEF_NONE; + + retval |= getrlimit(RLIMIT_AS, &limits[RLIMIT_AS].limit); + limits[RLIMIT_AS].src_soft = LIMITS_DEF_NONE; + limits[RLIMIT_AS].src_hard = LIMITS_DEF_NONE; + priority = 0; + login_limit = -2; + login_limit_def = LIMITS_DEF_NONE; + return retval; +} + +static void process_limit(int source, const char *lim_type, + const char *lim_item, const char *lim_value, + int ctrl) +{ + int limit_item; + int limit_type = 0; + long limit_value; + const char **endptr = &lim_value; + const char *value_orig = lim_value; + + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "%s: processing(%d) %s %s %s\n", + __FUNCTION__,source,lim_type,lim_item,lim_value); + + if (strcmp(lim_item, "cpu") == 0) + limit_item = RLIMIT_CPU; + else if (strcmp(lim_item, "fsize") == 0) + limit_item = RLIMIT_FSIZE; + else if (strcmp(lim_item, "data") == 0) + limit_item = RLIMIT_DATA; + else if (strcmp(lim_item, "stack") == 0) + limit_item = RLIMIT_STACK; + else if (strcmp(lim_item, "core") == 0) + limit_item = RLIMIT_CORE; + else if (strcmp(lim_item, "rss") == 0) + limit_item = RLIMIT_RSS; + else if (strcmp(lim_item, "nproc") == 0) + limit_item = RLIMIT_NPROC; + else if (strcmp(lim_item, "nofile") == 0) + limit_item = RLIMIT_NOFILE; + else if (strcmp(lim_item, "memlock") == 0) + limit_item = RLIMIT_MEMLOCK; + else if (strcmp(lim_item, "as") == 0) + limit_item = RLIMIT_AS; + else if (strcmp(lim_item, "maxlogins") == 0) { + limit_item = LIMIT_LOGIN; + flag_numsyslogins = 0; + } else if (strcmp(lim_item, "maxsyslogins") == 0) { + limit_item = LIMIT_NUMSYSLOGINS; + flag_numsyslogins = 1; + } else if (strcmp(lim_item, "priority") == 0) { + limit_item = LIMIT_PRI; + } else { + _pam_log(LOG_DEBUG,"unknown limit item '%s'", lim_item); + return; + } + + if (strcmp(lim_type,"soft")==0) + limit_type=LIMIT_SOFT; + else if (strcmp(lim_type, "hard")==0) + limit_type=LIMIT_HARD; + else if (strcmp(lim_type,"-")==0) + limit_type=LIMIT_SOFT | LIMIT_HARD; + else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) { + _pam_log(LOG_DEBUG,"unknown limit type '%s'", lim_type); + return; + } + + limit_value = strtol(lim_value, endptr, 10); + if (limit_value == 0 && value_orig == *endptr) { /* no chars read */ + if (strcmp(lim_value,"-") != 0) { + _pam_log(LOG_DEBUG,"wrong limit value '%s'", lim_value); + return; + } else + if (limit_item != LIMIT_LOGIN) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, + "'-' limit value valid for maxlogins type only"); + return; + } else + limit_value = -1; + } + + switch(limit_item) { + case RLIMIT_CPU: + limit_value *= 60; + break; + case RLIMIT_FSIZE: + case RLIMIT_DATA: + case RLIMIT_STACK: + case RLIMIT_CORE: + case RLIMIT_RSS: + case RLIMIT_MEMLOCK: + case RLIMIT_AS: + limit_value *= 1024; + break; + } + + if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS + && limit_item != LIMIT_PRI + ) { + if (limit_type & LIMIT_SOFT) { + if (limits[limit_item].src_soft < source) { + return; + } else { + limits[limit_item].limit.rlim_cur = limit_value; + limits[limit_item].src_soft = source; + } + } + if (limit_type & LIMIT_HARD) { + if (limits[limit_item].src_hard < source) { + return; + } else { + limits[limit_item].limit.rlim_max = limit_value; + limits[limit_item].src_hard = source; + } + } + } else + if (limit_item == LIMIT_PRI) { + /* additional check */ + priority = ((limit_value>0)?limit_value:0); + } else { + if (login_limit_def < source) { + return; + } else { + login_limit = limit_value; + login_limit_def = source; + } + } + return; +} + +static int parse_config_file(const char *uname, int ctrl) +{ + FILE *fil; + char buf[LINE_LENGTH]; + +#define CONF_FILE (conf_file[0])?conf_file:LIMITS_FILE + /* check for the LIMITS_FILE */ + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG,"reading settings from '%s'", CONF_FILE); + fil = fopen(CONF_FILE, "r"); + if (fil == NULL) { + _pam_log (LOG_WARNING, "can not read settings from %s", CONF_FILE); + return PAM_SERVICE_ERR; + } +#undef CONF_FILE + + /* init things */ + memset(buf, 0, sizeof(buf)); + /* start the show */ + while (fgets(buf, LINE_LENGTH, fil) != NULL) { + char domain[LINE_LENGTH]; + char ltype[LINE_LENGTH]; + char item[LINE_LENGTH]; + char value[LINE_LENGTH]; + int i,j; + char *tptr; + + tptr = buf; + /* skip the leading white space */ + while (*tptr && isspace(*tptr)) + tptr++; + strcpy(buf, (const char *)tptr); + + /* Rip off the comments */ + tptr = strchr(buf,'#'); + if (tptr) + *tptr = '\0'; + /* Rip off the newline char */ + tptr = strchr(buf,'\n'); + if (tptr) + *tptr = '\0'; + /* Anything left ? */ + if (!strlen(buf)) { + memset(buf, 0, sizeof(buf)); + continue; + } + + memset(domain, 0, sizeof(domain)); + memset(ltype, 0, sizeof(ltype)); + memset(item, 0, sizeof(item)); + memset(value, 0, sizeof(value)); + + i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value); + for(j=0; j < strlen(domain); j++) + domain[j]=tolower(domain[j]); + for(j=0; j < strlen(ltype); j++) + ltype[j]=tolower(ltype[j]); + for(j=0; j < strlen(item); j++) + item[j]=tolower(item[j]); + for(j=0; j < strlen(value); j++) + value[j]=tolower(value[j]); + + if (i == 4) { /* a complete line */ + if (strcmp(uname, domain) == 0) /* this user have a limit */ + process_limit(LIMITS_DEF_USER, ltype, item, value, ctrl); + else if (domain[0]=='@') { + if (is_on_group(uname, domain+1)) + process_limit(LIMITS_DEF_GROUP, ltype, item, value, ctrl); + } else if (strcmp(domain, "*") == 0) + process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, ctrl); + } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */ + if (strcmp(uname, domain) == 0) { + _pam_log(LOG_DEBUG, "no limits for '%s'", uname); + fclose(fil); + return PAM_IGNORE; + } else if (domain[0] == '@' && is_on_group(uname, domain+1)) { + _pam_log(LOG_DEBUG, "no limits for '%s' in group '%s'", + uname, domain+1); + fclose(fil); + return PAM_IGNORE; + } + } else { + _pam_log(LOG_DEBUG,"invalid line '%s'", buf); + } + } + fclose(fil); + return PAM_SUCCESS; +} + +static int setup_limits(const char * uname, int ctrl) +{ + int i; + int retval = PAM_SUCCESS; + + for (i=0; i limits[i].limit.rlim_max) + limits[i].limit.rlim_cur = limits[i].limit.rlim_max; + retval |= setrlimit(i, &limits[i].limit); + } + + if (retval != PAM_SUCCESS) + retval = LIMIT_ERR; + + retval=setpriority(PRIO_PROCESS, 0, priority); + + if (retval != PAM_SUCCESS) + retval = LIMIT_ERR; + + if (login_limit > 0) { + if (check_logins(uname, login_limit, ctrl) == LOGIN_ERR) + retval |= LOGIN_ERR; + } else if (login_limit == 0) + retval |= LOGIN_ERR; + return retval; +} + +/* now the session stuff */ +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int retval; + char *user_name; + struct passwd *pwd; + int ctrl; + + D(("called.")); + + memset(conf_file, 0, sizeof(conf_file)); + + ctrl = _pam_parse(argc, argv); + retval = pam_get_item( pamh, PAM_USER, (void*) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + _pam_log(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; + } + + setpwent(); + pwd = getpwnam(user_name); + endpwent(); + if (!pwd) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_WARNING, "open_session username '%s' does not exist", + user_name); + return PAM_SESSION_ERR; + } + + /* do not impose limits on UID 0 accounts */ + if (!pwd->pw_uid) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "user '%s' have UID 0 - no limits imposed", + user_name); + return PAM_SUCCESS; + } + + retval = init_limits(); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_WARNING, "can not initialize"); + return PAM_IGNORE; + } + + retval = parse_config_file(pwd->pw_name,ctrl); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_WARNING, "error parsing the configuration file"); + return PAM_IGNORE; + } + + retval = setup_limits(pwd->pw_name, ctrl); + if (retval & LOGIN_ERR) { + printf("\nToo many logins for '%s'\n",pwd->pw_name); + sleep(2); + return PAM_PERM_DENIED; + } + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* nothing to do */ + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_limits_modstruct = { + "pam_limits", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL +}; +#endif + +/* + * Copyright (c) Cristian Gafton, 1996-1997, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_listfile/.cvsignore b/modules/pam_listfile/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_listfile/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_listfile/Makefile b/modules/pam_listfile/Makefile new file mode 100644 index 00000000..02940390 --- /dev/null +++ b/modules/pam_listfile/Makefile @@ -0,0 +1,84 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# + +TITLE=pam_listfile + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_listfile/README b/modules/pam_listfile/README new file mode 100644 index 00000000..b65e7dbb --- /dev/null +++ b/modules/pam_listfile/README @@ -0,0 +1,25 @@ +SUMMARY: + pam_listfile: + Checks a specified item against a list in a file. + Options: + * item=[tty|user|rhost|ruser|group|shell] + * sense=[allow|deny] (action to take if found in file, + if the item is NOT found in the file, then + the opposite action is requested) + * file=/the/file/to/get/the/list/from + * onerr=[succeed|fail] (if something weird happens + such as unable to open the file, what to do?) + * apply=[user|@group] + restrict the user class for which the restriction + apply. Note that with item=[user|ruser|group] this + does not make sense, but for item=[tty|rhost|shell] + it have a meaning. (Cristian Gafton) + + Also checks to make sure that the list file is a plain + file and not world writable. + + - Elliot Lee , Red Hat Software. + v0.9 August 16, 1996. + +BUGS: + Bugs? diff --git a/modules/pam_listfile/pam_listfile.c b/modules/pam_listfile/pam_listfile.c new file mode 100644 index 00000000..8b1b45f0 --- /dev/null +++ b/modules/pam_listfile/pam_listfile.c @@ -0,0 +1,437 @@ +/* + * $Id$ + * + */ + +/* + * by Elliot Lee , Red Hat Software. July 25, 1996. + * log refused access error christopher mccrory 1998/7/11 + * + * This code began life as the pam_rootok module. + */ + +#ifdef linux +# define _SVID_SOURCE +# define _BSD_SOURCE +# define __USE_BSD +# define __USE_SVID +# define __USE_MISC +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +#include +#endif + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include +#include + +/* some syslogging */ + +#define LOCAL_LOG_PREFIX "PAM-listfile: " + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + vsyslog(LOG_AUTH | err, format, args); + va_end(args); +} + +/* checks if a user is on a list of members */ +static int is_on_list(char * const *list, const char *member) +{ + while (*list) { + if (strcmp(*list, member) == 0) + return 1; + list++; + } + return 0; +} + +/* Checks if a user is a member of a group */ +static int is_on_group(const char *user_name, const char *group_name) +{ + struct passwd *pwd; + struct group *grp, *pgrp; + char uname[BUFSIZ], gname[BUFSIZ]; + + if (!strlen(user_name)) + return 0; + if (!strlen(group_name)) + return 0; + bzero(uname, sizeof(uname)); + strncpy(uname, user_name, BUFSIZ-1); + bzero(gname, sizeof(gname)); + strncpy(gname, group_name, BUFSIZ-1); + + setpwent(); + pwd = getpwnam(uname); + endpwent(); + if (!pwd) + return 0; + + /* the info about this group */ + setgrent(); + grp = getgrnam(gname); + endgrent(); + if (!grp) + return 0; + + /* first check: is a member of the group_name group ? */ + if (is_on_list(grp->gr_mem, uname)) + return 1; + + /* next check: user primary group is group_name ? */ + setgrent(); + pgrp = getgrgid(pwd->pw_gid); + endgrent(); + if (!pgrp) + return 0; + if (!strcmp(pgrp->gr_name, gname)) + return 1; + + return 0; +} + +/* --- authentication management functions (only) --- */ + +/* Extended Items that are not directly available via pam_get_item() */ +#define EI_GROUP (1 << 0) +#define EI_SHELL (1 << 1) + +/* Constants for apply= parameter */ +#define APPLY_TYPE_NULL 0 +#define APPLY_TYPE_NONE 1 +#define APPLY_TYPE_USER 2 +#define APPLY_TYPE_GROUP 3 + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + int retval, i, citem=0, extitem=0, onerr=PAM_SERVICE_ERR, sense=2; + const char *citemp; + char *ifname=NULL; + char aline[256]; + char mybuf[256],myval[256]; + struct stat fileinfo; + FILE *inf; + char apply_val[256]; + int apply_type; + + /* Stuff for "extended" items */ + struct passwd *userinfo; + struct group *grpinfo; + char *itemlist[256]; /* Maximum of 256 items */ + + D(("called.")); + + apply_type=APPLY_TYPE_NULL; + memset(apply_val,0,sizeof(apply_val)); + + for(i=0; i < argc; i++) { + { + char *junk; + junk = (char *) malloc(strlen(argv[i])+1); + if (junk == NULL) { + return PAM_BUF_ERR; + } + strcpy(junk,argv[i]); + strncpy(mybuf,strtok(junk,"="),255); + strncpy(myval,strtok(NULL,"="),255); + free(junk); + } + if(!strcmp(mybuf,"onerr")) + if(!strcmp(myval,"succeed")) + onerr = PAM_SUCCESS; + else if(!strcmp(myval,"fail")) + onerr = PAM_SERVICE_ERR; + else + return PAM_SERVICE_ERR; + else if(!strcmp(mybuf,"sense")) + if(!strcmp(myval,"allow")) + sense=0; + else if(!strcmp(myval,"deny")) + sense=1; + else + return onerr; + else if(!strcmp(mybuf,"file")) { + ifname = (char *)malloc(strlen(myval)+1); + strcpy(ifname,myval); + } else if(!strcmp(mybuf,"item")) + if(!strcmp(myval,"user")) + citem = PAM_USER; + else if(!strcmp(myval,"tty")) + citem = PAM_TTY; + else if(!strcmp(myval,"rhost")) + citem = PAM_RHOST; + else if(!strcmp(myval,"ruser")) + citem = PAM_RUSER; + else { /* These items are related to the user, but are not + directly gettable with pam_get_item */ + citem = PAM_USER; + if(!strcmp(myval,"group")) + extitem = EI_GROUP; + else if(!strcmp(myval,"shell")) + extitem = EI_SHELL; + else + citem = 0; + } else if(!strcmp(mybuf,"apply")) { + apply_type=APPLY_TYPE_NONE; + if (myval[0]=='@') { + apply_type=APPLY_TYPE_GROUP; + strncpy(apply_val,myval+1,sizeof(apply_val)-1); + } else { + apply_type=APPLY_TYPE_USER; + strncpy(apply_val,myval,sizeof(apply_val)-1); + } + } else { + _pam_log(LOG_ERR,LOCAL_LOG_PREFIX "Unknown option: %s",mybuf); + return onerr; + } + } + + if(!citem) { + _pam_log(LOG_ERR, + LOCAL_LOG_PREFIX "Unknown item or item not specified"); + return onerr; + } else if(!ifname) { + _pam_log(LOG_ERR,LOCAL_LOG_PREFIX "List filename not specified"); + return onerr; + } else if(sense == 2) { + _pam_log(LOG_ERR, + LOCAL_LOG_PREFIX "Unknown sense or sense not specified"); + return onerr; + } else if( + (apply_type==APPLY_TYPE_NONE) || + ((apply_type!=APPLY_TYPE_NULL) && (*apply_val=='\0')) + ) { + _pam_log(LOG_ERR, + LOCAL_LOG_PREFIX "Invalid usage for apply= parameter"); + return onerr; + } + + /* Check if it makes sense to use the apply= parameter */ + if (apply_type != APPLY_TYPE_NULL) { + if((citem==PAM_USER) || (citem==PAM_RUSER)) { + _pam_log(LOG_WARNING, + LOCAL_LOG_PREFIX "Non-sense use for apply= parameter"); + apply_type=APPLY_TYPE_NULL; + } + if(extitem && (extitem==EI_GROUP)) { + _pam_log(LOG_WARNING, + LOCAL_LOG_PREFIX "Non-sense use for apply= parameter"); + apply_type=APPLY_TYPE_NULL; + } + } + + /* Short-circuit - test if this session apply for this user */ + { + const char *user_name; + int rval; + + rval=pam_get_user(pamh,&user_name,NULL); + if((rval==PAM_SUCCESS) && user_name[0]) { + /* Got it ? Valid ? */ + if(apply_type==APPLY_TYPE_USER) { + if(strcmp(user_name, apply_val)) { + /* Does not apply to this user */ +#ifdef DEBUG + _pam_log(LOG_DEBUG, + LOCAL_LOG_PREFIX "don't apply: apply=%s, user=%s", + apply_val,user_name); +#endif /* DEBUG */ + return PAM_IGNORE; + } + } else if(apply_type==APPLY_TYPE_GROUP) { + if(!is_on_group(user_name,apply_val)) { + /* Not a member of apply= group */ +#ifdef DEBUG + _pam_log(LOG_DEBUG, + LOCAL_LOG_PREFIX + "don't apply: %s not a member of group %s", + user_name,apply_val); +#endif /* DEBUG */ + return PAM_IGNORE; + } + } + } + } + + retval = pam_get_item(pamh,citem,(const void **)&citemp); + if(retval != PAM_SUCCESS) { + return onerr; + } + if((citem == PAM_USER) && !citemp) { + pam_get_user(pamh,&citemp,NULL); + if (retval != PAM_SUCCESS) + return PAM_SERVICE_ERR; + } + + if(!citemp || (strlen(citemp) <= 0)) { + /* The item was NULL - we are sure not to match */ + return sense?PAM_SUCCESS:PAM_AUTH_ERR; + } + + if(extitem) { + switch(extitem) { + case EI_GROUP: + setpwent(); + userinfo = getpwnam(citemp); + setgrent(); + grpinfo = getgrgid(userinfo->pw_gid); + itemlist[0] = x_strdup(grpinfo->gr_name); + setgrent(); + for (i=1; (i < sizeof(itemlist)/sizeof(itemlist[0])-1) && + (grpinfo = getgrent()); ) { + if (is_on_list(grpinfo->gr_mem,citemp)) { + itemlist[i++] = x_strdup(grpinfo->gr_name); + } + } + itemlist[i] = NULL; + endgrent(); + endpwent(); + break; + case EI_SHELL: + setpwent(); + userinfo = getpwnam(citemp); /* Assume that we have already gotten + PAM_USER in pam_get_item() - a valid + assumption since citem gets set to + PAM_USER in the extitem switch */ + citemp = userinfo->pw_shell; + endpwent(); + break; + default: + _pam_log(LOG_ERR, + LOCAL_LOG_PREFIX + "Internal weirdness, unknown extended item %d", + extitem); + return onerr; + } + } +#ifdef DEBUG + _pam_log(LOG_INFO, + LOCAL_LOG_PREFIX + "Got file = %s, item = %d, value = %s, sense = %d", + ifname, citem, citemp, sense); +#endif + if(lstat(ifname,&fileinfo)) { + _pam_log(LOG_ERR,LOCAL_LOG_PREFIX "Couldn't open %s",ifname); + return onerr; + } + + if((fileinfo.st_mode & S_IWOTH) + || !S_ISREG(fileinfo.st_mode)) { + /* If the file is world writable or is not a + normal file, return error */ + _pam_log(LOG_ERR,LOCAL_LOG_PREFIX + "%s is either world writable or not a normal file", + ifname); + return PAM_AUTH_ERR; + } + + inf = fopen(ifname,"r"); + if(inf == NULL) { /* Check that we opened it successfully */ + if (onerr == PAM_SERVICE_ERR) { + /* Only report if it's an error... */ + _pam_log(LOG_ERR,LOCAL_LOG_PREFIX "Error opening %s", ifname); + } + return onerr; + } + /* There should be no more errors from here on */ + retval=PAM_AUTH_ERR; + /* This loop assumes that PAM_SUCCESS == 0 + and PAM_AUTH_ERR != 0 */ +#ifdef DEBUG + assert(PAM_SUCCESS == 0); + assert(PAM_AUTH_ERR != 0); +#endif + if(extitem == EI_GROUP) { + while((fgets(aline,255,inf) != NULL) + && retval) { + if(aline[strlen(aline) - 1] == '\n') + aline[strlen(aline) - 1] = '\0'; + for(i=0;itemlist[i];) + /* If any of the items match, strcmp() == 0, and we get out + of this loop */ + retval = (strcmp(aline,itemlist[i++]) && retval); + } + for(i=0;itemlist[i];) + free(itemlist[i++]); + } else { + while((fgets(aline,255,inf) != NULL) + && retval) { + if(aline[strlen(aline) - 1] == '\n') + aline[strlen(aline) - 1] = '\0'; + retval = strcmp(aline,citemp); + } + } + fclose(inf); + free(ifname); + if ((sense && retval) || (!sense && !retval)) { +#ifdef DEBUG + _pam_log(LOG_INFO, LOCAL_LOG_PREFIX + "Returning PAM_SUCCESS, retval = %d", retval); +#endif + return PAM_SUCCESS; + } + else { + const char *service, *user_name; +#ifdef DEBUG + _pam_log(LOG_INFO,LOCAL_LOG_PREFIX + "Returning PAM_AUTH_ERR, retval = %d", retval); +#endif + (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&service); + (void) pam_get_user(pamh, &user_name, NULL); + _pam_log(LOG_ALERT,LOCAL_LOG_PREFIX "Refused user %s for service %s", + user_name, service); + return PAM_AUTH_ERR; + } +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_listfile_modstruct = { + "pam_listfile", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ + diff --git a/modules/pam_mail/.cvsignore b/modules/pam_mail/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_mail/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_mail/Makefile b/modules/pam_mail/Makefile new file mode 100644 index 00000000..64f73b0b --- /dev/null +++ b/modules/pam_mail/Makefile @@ -0,0 +1,113 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:45 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:17 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.3 1997/04/05 06:37:45 morgan +# fakeroot +# +# Revision 1.2 1997/02/15 16:07:22 morgan +# optional libpwdb compilation +# +# Revision 1.1 1997/01/04 20:32:52 morgan +# Initial revision +# +# +# +# Created by Andrew Morgan 1996/12/8 +# + +TITLE=pam_mail + +ifndef STATIC +ifeq ($(HAVE_PWDBLIB),yes) +CFLAGS += -DWANT_PWDB +EXTRALIB = -lpwdb +endif +endif + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(EXTRALIB) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(EXTRALIB) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_mail/README b/modules/pam_mail/README new file mode 100644 index 00000000..155bd1db --- /dev/null +++ b/modules/pam_mail/README @@ -0,0 +1,17 @@ +This is the README for pam_mail +------------------------------- + +This PAM module tells the User that he has new/unread email. + +Options for: +auth: for authentication it provides pam_authenticate() and + pam_setcred() hooks. + + "debug" write more information to syslog + "dir=maildir" users mailbox is maildir/ + "hash=count" mail directory hash depth + "close" print message also on logout + "nopen" print message not on login + "noenv" don't set the MAIL environment variable + "empty" also print message if user has no mail + diff --git a/modules/pam_mail/pam_mail.c b/modules/pam_mail/pam_mail.c new file mode 100644 index 00000000..f67b6f02 --- /dev/null +++ b/modules/pam_mail/pam_mail.c @@ -0,0 +1,504 @@ +/* pam_mail module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + * $HOME additions by David Kinchlea 1997/1/7 + * mailhash additions by Chris Adams 1998/7/11 + */ + +#define DEFAULT_MAIL_DIRECTORY "/var/spool/mail" +#define MAIL_FILE_FORMAT "%s%s/%s" +#define MAIL_ENV_NAME "MAIL" +#define MAIL_ENV_FORMAT MAIL_ENV_NAME "=%s" +#define YOUR_MAIL_VERBOSE_FORMAT "You have %s mail in %s." +#define YOUR_MAIL_STANDARD_FORMAT "You have %smail." +#define NO_MAIL_STANDARD_FORMAT "No mail." + +#define _BSD_SOURCE + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WANT_PWDB +#include +#endif + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_SESSION +#define PAM_SM_AUTH + +#include +#include + +/* some syslogging */ + +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-mail", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 0x0001 +#define PAM_NO_LOGIN 0x0002 +#define PAM_LOGOUT_TOO 0x0004 +#define PAM_NEW_MAIL_DIR 0x0010 +#define PAM_MAIL_SILENT 0x0020 +#define PAM_NO_ENV 0x0040 +#define PAM_HOME_MAIL 0x0100 +#define PAM_EMPTY_TOO 0x0200 +#define PAM_STANDARD_MAIL 0x0400 +#define PAM_QUIET_MAIL 0x1000 + +static int _pam_parse(int flags, int argc, const char **argv, char **maildir, + int *hashcount) +{ + int ctrl=0; + + if (flags & PAM_SILENT) { + ctrl |= PAM_MAIL_SILENT; + } + + *hashcount = 0; + + /* step through arguments */ + for (; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strcmp(*argv,"quiet")) + ctrl |= PAM_QUIET_MAIL; + else if (!strcmp(*argv,"standard")) + ctrl |= PAM_STANDARD_MAIL | PAM_EMPTY_TOO; + else if (!strncmp(*argv,"dir=",4)) { + *maildir = x_strdup(4+*argv); + if (*maildir != NULL) { + D(("new mail directory: %s", *maildir)); + ctrl |= PAM_NEW_MAIL_DIR; + } else { + _log_err(LOG_CRIT, + "failed to duplicate mail directory - ignored"); + } + } else if (!strncmp(*argv,"hash=",5)) { + char *ep = NULL; + *hashcount = strtol(*argv+5,&ep,10); + if (!ep || (*hashcount < 0)) { + *hashcount = 0; + } + } else if (!strcmp(*argv,"close")) { + ctrl |= PAM_LOGOUT_TOO; + } else if (!strcmp(*argv,"nopen")) { + ctrl |= PAM_NO_LOGIN; + } else if (!strcmp(*argv,"noenv")) { + ctrl |= PAM_NO_ENV; + } else if (!strcmp(*argv,"empty")) { + ctrl |= PAM_EMPTY_TOO; + } else { + _log_err(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + if ((*hashcount != 0) && !(ctrl & PAM_NEW_MAIL_DIR)) { + *maildir = x_strdup(DEFAULT_MAIL_DIRECTORY); + ctrl |= PAM_NEW_MAIL_DIR; + } + + return ctrl; +} + +/* a front end for conversations */ + +static int converse(pam_handle_t *pamh, int ctrl, int nargs + , struct pam_message **message + , struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; + if ( retval == PAM_SUCCESS ) { + + retval = conv->conv(nargs, ( const struct pam_message ** ) message + , response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && (PAM_DEBUG_ARG & ctrl) ) { + _log_err(LOG_DEBUG, "conversation failure [%s]" + , pam_strerror(pamh, retval)); + } + + } else { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + , pam_strerror(pamh, retval)); + } + + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +static int get_folder(pam_handle_t *pamh, int ctrl, + char **path_mail, char **folder_p, int hashcount) +{ + int retval; + const char *user, *path; + char *folder; + const struct passwd *pwd=NULL; + + retval = pam_get_user(pamh, &user, NULL); + if (retval != PAM_SUCCESS || user == NULL) { + _log_err(LOG_ERR, "no user specified"); + return PAM_USER_UNKNOWN; + } + + if (ctrl & PAM_NEW_MAIL_DIR) { + path = *path_mail; + if (*path == '~') { /* support for $HOME delivery */ + pwd = getpwnam(user); + if (pwd == NULL) { + _log_err(LOG_ERR, "user [%s] unknown", user); + _pam_overwrite(*path_mail); + _pam_drop(*path_mail); + return PAM_USER_UNKNOWN; + } + /* + * "~/xxx" and "~xxx" are treated as same + */ + if (!*++path || (*path == '/' && !*++path)) { + _log_err(LOG_ALERT, "badly formed mail path [%s]", *path_mail); + _pam_overwrite(*path_mail); + _pam_drop(*path_mail); + return PAM_ABORT; + } + ctrl |= PAM_HOME_MAIL; + if (hashcount != 0) { + _log_err(LOG_ALERT, "can't do hash= and home directory mail"); + } + } + } else { + path = DEFAULT_MAIL_DIRECTORY; + } + + /* put folder together */ + + if (ctrl & PAM_HOME_MAIL) { + folder = malloc(sizeof(MAIL_FILE_FORMAT) + +strlen(pwd->pw_dir)+strlen(path)); + } else { + folder = malloc(sizeof(MAIL_FILE_FORMAT)+strlen(path)+strlen(user) + +2*hashcount); + } + + if (folder != NULL) { + if (ctrl & PAM_HOME_MAIL) { + sprintf(folder, MAIL_FILE_FORMAT, pwd->pw_dir, "", path); + } else { + int i; + char *hash = malloc(2*hashcount+1); + + if (hash) { + for (i = 0; i < hashcount; i++) { + hash[2*i] = '/'; + hash[2*i+1] = user[i]; + } + hash[2*i] = '\0'; + sprintf(folder, MAIL_FILE_FORMAT, path, hash, user); + _pam_overwrite(hash); + _pam_drop(hash); + } else { + sprintf(folder, "error"); + } + } + D(("folder =[%s]", folder)); + } + + /* tidy up */ + + _pam_overwrite(*path_mail); + _pam_drop(*path_mail); + user = NULL; + + if (folder == NULL) { + _log_err(LOG_CRIT, "out of memory for mail folder"); + return PAM_BUF_ERR; + } + + *folder_p = folder; + folder = NULL; + + return PAM_SUCCESS; +} + +static const char *get_mail_status(int ctrl, const char *folder) +{ + const char *type = NULL; + static char dir[256]; + struct stat mail_st; + struct dirent **namelist; + int i; + + if (stat(folder, &mail_st) == 0) { + if (S_ISDIR(mail_st.st_mode)) { /* Assume Maildir format */ + sprintf(dir, "%.250s/new", folder); + i = scandir(dir, &namelist, 0, alphasort); + if (i > 2) { + type = "new"; + while (--i) + free(namelist[i]); + } else { + while (--i >= 0) + free(namelist[i]); + sprintf(dir, "%.250s/cur", folder); + i = scandir(dir, &namelist, 0, alphasort); + if (i > 2) { + type = "old"; + while (--i) + free(namelist[i]); + } else if (ctrl & PAM_EMPTY_TOO) { + while (--i >= 0) + free(namelist[i]); + type = "no"; + } else { + type = NULL; + } + } + } else { + if (mail_st.st_size > 0) { + if (mail_st.st_atime < mail_st.st_mtime) /* new */ + type = (ctrl & PAM_STANDARD_MAIL) ? "new " : "new"; + else /* old */ + type = (ctrl & PAM_STANDARD_MAIL) ? "" : "old"; + } else if (ctrl & PAM_EMPTY_TOO) { + type = "no"; + } else { + type = NULL; + } + } + } + + memset(dir, 0, 256); + memset(&mail_st, 0, sizeof(mail_st)); + D(("user has %s mail in %s folder", type, folder)); + return type; +} + +static int report_mail(pam_handle_t *pamh, int ctrl + , const char *type, const char *folder) +{ + int retval; + + if (!(ctrl & PAM_MAIL_SILENT) || ((ctrl & PAM_QUIET_MAIL) && strcmp(type, "new"))) { + char *remark; + + if (ctrl & PAM_STANDARD_MAIL) + if (!strcmp(type, "no")) + remark = malloc(strlen(NO_MAIL_STANDARD_FORMAT)+1); + else + remark = malloc(strlen(YOUR_MAIL_STANDARD_FORMAT)+strlen(type)+1); + else + remark = malloc(strlen(YOUR_MAIL_VERBOSE_FORMAT)+strlen(type)+strlen(folder)+1); + if (remark == NULL) { + retval = PAM_BUF_ERR; + } else { + struct pam_message msg[1], *mesg[1]; + struct pam_response *resp=NULL; + + if (ctrl & PAM_STANDARD_MAIL) + if (!strcmp(type, "no")) + sprintf(remark, NO_MAIL_STANDARD_FORMAT); + else + sprintf(remark, YOUR_MAIL_STANDARD_FORMAT, type); + else + sprintf(remark, YOUR_MAIL_VERBOSE_FORMAT, type, folder); + + mesg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = remark; + + retval = converse(pamh, ctrl, 1, mesg, &resp); + + _pam_overwrite(remark); + _pam_drop(remark); + if (resp) + _pam_drop_reply(resp, 1); + } + } else { + D(("keeping quiet")); + retval = PAM_SUCCESS; + } + + D(("returning %s", pam_strerror(pamh, retval))); + return retval; +} + +static int _do_mail(pam_handle_t *, int, int, const char **, int); + +/* --- authentication functions --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +/* Checking mail as part of authentication */ +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + if (!(flags & (PAM_ESTABLISH_CRED|PAM_DELETE_CRED))) + return PAM_IGNORE; + return _do_mail(pamh,flags,argc,argv,(flags & PAM_ESTABLISH_CRED)); +} + +/* --- session management functions --- */ + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return _do_mail(pamh,flags,argc,argv,0);; +} + +/* Checking mail as part of the session management */ +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return _do_mail(pamh,flags,argc,argv,1); +} + + +/* --- The Beaf (Tm) --- */ + +static int _do_mail(pam_handle_t *pamh, int flags, int argc, + const char **argv, int est) +{ + int retval, ctrl, hashcount; + char *path_mail=NULL, *folder; + const char *type; + + /* + * this module (un)sets the MAIL environment variable, and checks if + * the user has any new mail. + */ + + ctrl = _pam_parse(flags, argc, argv, &path_mail, &hashcount); + + /* Do we have anything to do? */ + + if (flags & PAM_SILENT) + return PAM_SUCCESS; + + /* which folder? */ + + retval = get_folder(pamh, ctrl, &path_mail, &folder, hashcount); + if (retval != PAM_SUCCESS) { + D(("failed to find folder")); + return retval; + } + + /* set the MAIL variable? */ + + if (!(ctrl & PAM_NO_ENV) && est) { + char *tmp; + + tmp = malloc(strlen(folder)+sizeof(MAIL_ENV_FORMAT)); + if (tmp != NULL) { + sprintf(tmp, MAIL_ENV_FORMAT, folder); + D(("setting env: %s", tmp)); + retval = pam_putenv(pamh, tmp); + _pam_overwrite(tmp); + _pam_drop(tmp); + if (retval != PAM_SUCCESS) { + _pam_overwrite(folder); + _pam_drop(folder); + _log_err(LOG_CRIT, "unable to set " MAIL_ENV_NAME " variable"); + return retval; + } + } else { + _log_err(LOG_CRIT, "no memory for " MAIL_ENV_NAME " variable"); + _pam_overwrite(folder); + _pam_drop(folder); + return retval; + } + } else { + D(("not setting " MAIL_ENV_NAME " variable")); + } + + /* + * OK. we've got the mail folder... what about its status? + */ + + if ((est && !(ctrl & PAM_NO_LOGIN)) + || (!est && (ctrl & PAM_LOGOUT_TOO))) { + type = get_mail_status(ctrl, folder); + if (type != NULL) { + retval = report_mail(pamh, ctrl, type, folder); + type = NULL; + } + } + + /* Delete environment variable? */ + if (!est) + (void) pam_putenv(pamh, MAIL_ENV_NAME); + + _pam_overwrite(folder); /* clean up */ + _pam_drop(folder); + + /* indicate success or failure */ + + return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_mail_modstruct = { + "pam_mail", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_mkhomedir/.cvsignore b/modules/pam_mkhomedir/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_mkhomedir/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_mkhomedir/Makefile b/modules/pam_mkhomedir/Makefile new file mode 100644 index 00000000..7c9f5d3a --- /dev/null +++ b/modules/pam_mkhomedir/Makefile @@ -0,0 +1,90 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/3/11 +# + +# Convenient defaults for compiling independently of the full source +# tree. + +# + +TITLE=pam_mkhomedir + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(LINKLIBS) -lpam +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< diff --git a/modules/pam_mkhomedir/pam_mkhomedir.c b/modules/pam_mkhomedir/pam_mkhomedir.c new file mode 100644 index 00000000..ec05993d --- /dev/null +++ b/modules/pam_mkhomedir/pam_mkhomedir.c @@ -0,0 +1,370 @@ +/* PAM Make Home Dir module + + This module will create a users home directory if it does not exist + when the session begins. This allows users to be present in central + database (such as nis, kerb or ldap) without using a distributed + file system or pre-creating a large number of directories. + + Here is a sample /etc/pam.d/login file for Debian GNU/Linux + 2.1: + + auth requisite pam_securetty.so + auth sufficient pam_ldap.so + auth required pam_pwdb.so + auth optional pam_group.so + auth optional pam_mail.so + account requisite pam_time.so + account sufficient pam_ldap.so + account required pam_pwdb.so + session required pam_mkhomedir.so skel=/etc/skel/ umask=0022 + session required pam_pwdb.so + session optional pam_lastlog.so + password required pam_pwdb.so + + Released under the GNU LGPL version 2 or later + Originally written by Jason Gunthorpe Feb 1999 + Structure taken from pam_lastlogin by Andrew Morgan + 1996 + */ + +/* I want snprintf dammit */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_SESSION + +#include +#include + +/* argument parsing */ +#define MKHOMEDIR_DEBUG 020 /* keep quiet about things */ +#define MKHOMEDIR_QUIET 040 /* keep quiet about things */ + +static unsigned int UMask = 0022; +static char SkelDir[BUFSIZ] = "/etc/skel"; + +/* some syslogging */ +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-mkhomedir", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static int _pam_parse(int flags, int argc, const char **argv) +{ + int ctrl = 0; + + /* does the appliction require quiet? */ + if ((flags & PAM_SILENT) == PAM_SILENT) + ctrl |= MKHOMEDIR_QUIET; + + /* step through arguments */ + for (; argc-- > 0; ++argv) + { + if (!strcmp(*argv, "silent")) + { + ctrl |= MKHOMEDIR_QUIET; + } + else if (!strncmp(*argv,"umask=",6)) + UMask = strtol(*argv+6,0,0); + else if (!strncmp(*argv,"skel=",5)) + strcpy(SkelDir,*argv+5); + else + { + _log_err(LOG_ERR, "unknown option; %s", *argv); + } + } + + D(("ctrl = %o", ctrl)); + return ctrl; +} + +/* This common function is used to send a message to the applications + conversion function. Our only use is to ask the application to print + an informative message that we are creating a home directory */ +static int converse(pam_handle_t * pamh, int ctrl, int nargs + ,struct pam_message **message + ,struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (retval == PAM_SUCCESS) + { + + retval = conv->conv(nargs, (const struct pam_message **) message + ,response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && (ctrl & MKHOMEDIR_DEBUG)) + { + _log_err(LOG_DEBUG, "conversation failure [%s]" + ,pam_strerror(pamh, retval)); + } + + } + else + { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + ,pam_strerror(pamh, retval)); + } + + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +/* Ask the application to display a short text string for us. */ +static int make_remark(pam_handle_t * pamh, int ctrl, const char *remark) +{ + int retval; + + if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET) + { + struct pam_message msg[1], *mesg[1]; + struct pam_response *resp = NULL; + + mesg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = remark; + + retval = converse(pamh, ctrl, 1, mesg, &resp); + + msg[0].msg = NULL; + if (resp) + { + _pam_drop_reply(resp, 1); + } + } + else + { + D(("keeping quiet")); + retval = PAM_SUCCESS; + } + + D(("returning %s", pam_strerror(pamh, retval))); + return retval; +} + +/* Do the actual work of creating a home dir */ +static int create_homedir(pam_handle_t * pamh, int ctrl, + const struct passwd *pwd) +{ + char *remark; + DIR *D; + struct dirent *Dir; + + /* Some scratch space */ + remark = malloc(BUFSIZ); + if (remark == NULL) + { + D(("no memory for last login remark")); + return PAM_BUF_ERR; + } + + /* Mention what is happening, if the notification fails that is OK */ + if (snprintf(remark,BUFSIZ,"Creating home directory '%s'.", + pwd->pw_dir) == -1) + return PAM_PERM_DENIED; + + make_remark(pamh, ctrl, remark); + + /* Crete the home directory */ + if (mkdir(pwd->pw_dir,0700) != 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to create home directory %s",pwd->pw_dir); + return PAM_PERM_DENIED; + } + if (chmod(pwd->pw_dir,0777 & (~UMask)) != 0 || + chown(pwd->pw_dir,pwd->pw_uid,pwd->pw_gid) != 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to chance perms on home directory %s",pwd->pw_dir); + return PAM_PERM_DENIED; + } + + /* See if we need to copy the skel dir over. */ + if (SkelDir[0] == 0) + { + free(remark); + return PAM_SUCCESS; + } + + /* Scan the directory */ + D = opendir(SkelDir); + if (D == 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to read directory %s",SkelDir); + return PAM_PERM_DENIED; + } + + for (Dir = readdir(D); Dir != 0; Dir = readdir(D)) + { + int SrcFd; + int DestFd; + int Res; + struct stat St; + + /* Skip some files.. */ + if (strcmp(Dir->d_name,".") == 0 || + strcmp(Dir->d_name,"..") == 0) + continue; + + /* Check if it is a directory */ + snprintf(remark,BUFSIZ,"%s/%s",SkelDir,Dir->d_name); + if (stat(remark,&St) != 0) + continue; + if (S_ISDIR(St.st_mode)) + { + snprintf(remark,BUFSIZ,"%s/%s",pwd->pw_dir,Dir->d_name); + if (mkdir(remark,(St.st_mode | 0222) & (~UMask)) != 0 || + chmod(remark,(St.st_mode | 0222) & (~UMask)) != 0 || + chown(remark,pwd->pw_uid,pwd->pw_gid) != 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to change perms on copy %s",remark); + return PAM_PERM_DENIED; + } + continue; + } + + /* Open the source file */ + if ((SrcFd = open(remark,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to open src file %s",remark); + return PAM_PERM_DENIED; + } + stat(remark,&St); + + /* Open the dest file */ + snprintf(remark,BUFSIZ,"%s/%s",pwd->pw_dir,Dir->d_name); + if ((DestFd = open(remark,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0) + { + close(SrcFd); + free(remark); + _log_err(LOG_DEBUG, "unable to open dest file %s",remark); + return PAM_PERM_DENIED; + } + + /* Set the proper ownership and permissions for the module. We make + the file a+w and then mask it with the set mask. This preseves + execute bits */ + if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 || + fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0) + { + free(remark); + _log_err(LOG_DEBUG, "unable to chang perms on copy %s",remark); + return PAM_PERM_DENIED; + } + + /* Copy the file */ + do + { + Res = read(SrcFd,remark,BUFSIZ); + if (Res < 0 || write(DestFd,remark,Res) != Res) + { + close(SrcFd); + close(DestFd); + free(remark); + _log_err(LOG_DEBUG, "unable to perform IO"); + return PAM_PERM_DENIED; + } + } + while (Res != 0); + close(SrcFd); + close(DestFd); + } + + free(remark); + return PAM_SUCCESS; +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc + ,const char **argv) +{ + int retval, ctrl; + const char *user; + const struct passwd *pwd; + struct stat St; + + /* Parse the flag values */ + ctrl = _pam_parse(flags, argc, argv); + + /* Determine the user name so we can get the home directory */ + retval = pam_get_item(pamh, PAM_USER, (const void **) &user); + if (retval != PAM_SUCCESS || user == NULL || *user == '\0') + { + _log_err(LOG_NOTICE, "user unknown"); + return PAM_USER_UNKNOWN; + } + + /* Get the password entry */ + pwd = getpwnam(user); + if (pwd == NULL) + { + D(("couldn't identify user %s", user)); + return PAM_CRED_INSUFFICIENT; + } + + /* Stat the home directory, if something exists then we assume it is + correct and return a success*/ + if (stat(pwd->pw_dir,&St) == 0) + return PAM_SUCCESS; + + return create_homedir(pamh,ctrl,pwd); +} + +/* Ignore */ +PAM_EXTERN +int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +/* static module data */ +struct pam_module _pam_mkhomedir_modstruct = +{ + "pam_mkhomedir", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; + +#endif diff --git a/modules/pam_motd/.cvsignore b/modules/pam_motd/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_motd/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_motd/Makefile b/modules/pam_motd/Makefile new file mode 100644 index 00000000..ef9f3113 --- /dev/null +++ b/modules/pam_motd/Makefile @@ -0,0 +1,90 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/3/11 +# + +# Convenient defaults for compiling independently of the full source +# tree. + +# + +TITLE=pam_motd + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(LINKLIBS) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< diff --git a/modules/pam_motd/pam_motd.c b/modules/pam_motd/pam_motd.c new file mode 100644 index 00000000..98976b66 --- /dev/null +++ b/modules/pam_motd/pam_motd.c @@ -0,0 +1,119 @@ +/* pam_motd module */ + +/* + * Modified for pam_motd by Ben Collins + * + * Based off of: + * $Id$ + * + * Written by Michael K. Johnson 1996/10/24 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_SESSION +#define DEFAULT_MOTD "/etc/motd" + +#include + +/* --- session management functions (only) --- */ + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_IGNORE; +} + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + int retval = PAM_IGNORE; + int fd; + char *mtmp=NULL, *motd_path=NULL; + struct pam_conv *conversation; + struct pam_message message; + struct pam_message *pmessage = &message; + struct pam_response *resp = NULL; + struct stat st; + + if (flags & PAM_SILENT) { + return retval; + } + + for (; argc-- > 0; ++argv) { + if (!strncmp(*argv,"motd=",5)) { + motd_path = (char *) strdup(5+*argv); + if (motd_path != NULL) { + D(("set motd path: %s", motd_path)); + } else { + D(("failed to duplicate motd path - ignored")); + } + } + } + + if (motd_path == NULL) + motd_path = DEFAULT_MOTD; + + message.msg_style = PAM_TEXT_INFO; + + if ((fd = open(motd_path, O_RDONLY, 0)) >= 0) { + /* fill in message buffer with contents of motd */ + if ((fstat(fd, &st) < 0) || !st.st_size) + return retval; + message.msg = mtmp = malloc(st.st_size+1); + /* if malloc failed... */ + if (!message.msg) return retval; + read(fd, mtmp, st.st_size); + if (mtmp[st.st_size-1] == '\n') + mtmp[st.st_size-1] = '\0'; + else + mtmp[st.st_size] = '\0'; + close(fd); + /* Use conversation function to give user contents of motd */ + pam_get_item(pamh, PAM_CONV, (const void **)&conversation); + conversation->conv(1, (const struct pam_message **)&pmessage, + &resp, conversation->appdata_ptr); + free(mtmp); + if (resp) + _pam_drop_reply(resp, 1); + } + + return retval; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_motd_modstruct = { + "pam_motd", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_nologin/.cvsignore b/modules/pam_nologin/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_nologin/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_nologin/Makefile b/modules/pam_nologin/Makefile new file mode 100644 index 00000000..03d779bf --- /dev/null +++ b/modules/pam_nologin/Makefile @@ -0,0 +1,86 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Michael K. Johnson 1996/10/24 +# + +TITLE=pam_nologin + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + mkdir -p ./dynamic +endif +ifdef STATIC + mkdir -p ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) -lc +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + mkdir -p $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + install -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_nologin/README b/modules/pam_nologin/README new file mode 100644 index 00000000..0586de66 --- /dev/null +++ b/modules/pam_nologin/README @@ -0,0 +1,12 @@ +# $Id$ +# + +This module always lets root in; it lets other users in only if the file +/etc/nologin doesn't exist. In any case, if /etc/nologin exists, it's +contents are displayed to the user. + +module services provided: + + auth _authentication and _setcred (blank) + +Michael K. Johnson diff --git a/modules/pam_nologin/pam_nologin.c b/modules/pam_nologin/pam_nologin.c new file mode 100644 index 00000000..306619e1 --- /dev/null +++ b/modules/pam_nologin/pam_nologin.c @@ -0,0 +1,130 @@ +/* pam_nologin module */ + +/* + * $Id$ + * + * Written by Michael K. Johnson 1996/10/24 + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:46 agmorgan + * Initial revision + * + * Revision 1.1.1.1 1998/07/12 05:17:17 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.4 1997/04/05 06:36:47 morgan + * display message when the user is unknown + * + * Revision 1.3 1996/12/01 03:00:54 morgan + * added prototype to conversation, gave static structure name of module + * + * Revision 1.2 1996/11/10 21:02:31 morgan + * compile against .53 + * + * Revision 1.1 1996/10/25 03:19:36 morgan + * Initial revision + * + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + int retval = PAM_SUCCESS; + int fd; + const char *username; + char *mtmp=NULL; + struct passwd *user_pwd; + struct pam_conv *conversation; + struct pam_message message; + struct pam_message *pmessage = &message; + struct pam_response *resp = NULL; + struct stat st; + + if ((fd = open("/etc/nologin", O_RDONLY, 0)) >= 0) { + /* root can still log in; lusers cannot */ + if ((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) + || !username) { + return PAM_SERVICE_ERR; + } + user_pwd = getpwnam(username); + if (user_pwd && user_pwd->pw_uid == 0) { + message.msg_style = PAM_TEXT_INFO; + } else { + if (!user_pwd) { + retval = PAM_USER_UNKNOWN; + } else { + retval = PAM_AUTH_ERR; + } + message.msg_style = PAM_ERROR_MSG; + } + + /* fill in message buffer with contents of /etc/nologin */ + if (fstat(fd, &st) < 0) /* give up trying to display message */ + return retval; + message.msg = mtmp = malloc(st.st_size+1); + /* if malloc failed... */ + if (!message.msg) return retval; + read(fd, mtmp, st.st_size); + mtmp[st.st_size] = '\000'; + + /* Use conversation function to give user contents of /etc/nologin */ + pam_get_item(pamh, PAM_CONV, (const void **)&conversation); + conversation->conv(1, (const struct pam_message **)&pmessage, + &resp, conversation->appdata_ptr); + free(mtmp); + if (resp) + _pam_drop_reply(resp, 1); + } + + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_nologin_modstruct = { + "pam_nologin", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_permit/.cvsignore b/modules/pam_permit/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_permit/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_permit/Makefile b/modules/pam_permit/Makefile new file mode 100644 index 00000000..c291599e --- /dev/null +++ b/modules/pam_permit/Makefile @@ -0,0 +1,132 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:46 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.8 1997/04/05 06:33:25 morgan +# fakeroot +# +# Revision 1.7 1997/02/15 19:02:27 morgan +# updated email address +# +# Revision 1.6 1996/11/10 20:14:34 morgan +# cross platform support +# +# Revision 1.5 1996/09/05 06:32:45 morgan +# ld --> gcc +# +# Revision 1.4 1996/05/26 15:49:25 morgan +# make dynamic and static dirs +# +# Revision 1.3 1996/05/26 04:04:26 morgan +# automated static support +# +# Revision 1.2 1996/03/16 17:56:38 morgan +# tidied up +# +# +# Created by Andrew Morgan 1996/3/11 +# + +# Convenient defaults for compiling independently of the full source +# tree. +ifndef FULL_LINUX_PAM_SOURCE_TREE +export DYNAMIC=-DPAM_DYNAMIC +export CC=gcc +export CFLAGS=-O2 -Dlinux -DLINUX_PAM \ + -ansi -D_POSIX_SOURCE -Wall -Wwrite-strings \ + -Wpointer-arith -Wcast-qual -Wcast-align -Wtraditional \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Winline \ + -Wshadow -pedantic -fPIC +export MKDIR=mkdir -p +export LD_D=gcc -shared -Xlinker -x +endif + +# +# + +TITLE=pam_permit + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(TARGET_ARCH) -c $< -o $@ + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) +endif + +ifdef DYNAMIC +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) +endif + +ifdef STATIC +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_permit/README b/modules/pam_permit/README new file mode 100644 index 00000000..c399710c --- /dev/null +++ b/modules/pam_permit/README @@ -0,0 +1,4 @@ +# $Id$ +# + +this module always returns PAM_SUCCESS, it ignores all options. diff --git a/modules/pam_permit/pam_permit.c b/modules/pam_permit/pam_permit.c new file mode 100644 index 00000000..cf11a2dc --- /dev/null +++ b/modules/pam_permit/pam_permit.c @@ -0,0 +1,128 @@ +/* pam_permit module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:46 agmorgan + * Initial revision + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.5 1997/02/15 19:03:15 morgan + * fixed email address + * + * Revision 1.4 1997/02/15 16:03:10 morgan + * force a name for user + * + * Revision 1.3 1996/06/02 08:10:14 morgan + * updated for new static protocol + * + */ + +#define DEFAULT_USER "nobody" + +#include + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD + +#include +#include + +/* --- authentication management functions --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int retval; + const char *user=NULL; + + /* + * authentication requires we know who the user wants to be + */ + retval = pam_get_user(pamh, &user, NULL); + if (retval != PAM_SUCCESS) { + D(("get user returned error: %s", pam_strerror(pamh,retval))); + return retval; + } + if (user == NULL || *user == '\0') { + D(("username not known")); + pam_set_item(pamh, PAM_USER, (const void *) DEFAULT_USER); + } + user = NULL; /* clean up */ + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +/* --- account management functions --- */ + +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +/* --- password management --- */ + +PAM_EXTERN +int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +/* --- session management --- */ + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + +/* end of module definition */ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_permit_modstruct = { + "pam_permit", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif diff --git a/modules/pam_pwdb/.cvsignore b/modules/pam_pwdb/.cvsignore new file mode 100644 index 00000000..f0420bac --- /dev/null +++ b/modules/pam_pwdb/.cvsignore @@ -0,0 +1,2 @@ +dynamic +pwdb_chkpwd diff --git a/modules/pam_pwdb/BUGS b/modules/pam_pwdb/BUGS new file mode 100644 index 00000000..e7170b70 --- /dev/null +++ b/modules/pam_pwdb/BUGS @@ -0,0 +1,14 @@ +$Id$ + +$Log$ +Revision 1.1 2000/06/20 22:11:46 agmorgan +Initial revision + +Revision 1.1.1.1 1998/07/12 05:17:16 morgan +Linux PAM sources pre-0.66 + +Revision 1.2 1996/09/05 06:36:16 morgan +revised for .52 to be released + + +As of Linux-PAM-0.52 this is new. No known bugs yet. diff --git a/modules/pam_pwdb/CHANGELOG b/modules/pam_pwdb/CHANGELOG new file mode 100644 index 00000000..7bad5cd8 --- /dev/null +++ b/modules/pam_pwdb/CHANGELOG @@ -0,0 +1,10 @@ +$Id$ + +Tue Apr 23 12:28:09 EDT 1996 (Alexander O. Yuriev alex@bach.cis.temple.edu) + + * PAM_DISALLOW_NULL_AUTHTOK implemented in the authentication module + * pam_sm_open_session() and pam_sm_close_session() implemented + A new "trace" flag added to flags of /etc/pam.conf. Using this + flag system administrator is able to make pam_unix module provide + very extensive audit trail sent so syslog with LOG_AUTHPRIV level. + * pam_sm_set_cred() is done diff --git a/modules/pam_pwdb/Makefile b/modules/pam_pwdb/Makefile new file mode 100644 index 00000000..fcb7aec4 --- /dev/null +++ b/modules/pam_pwdb/Makefile @@ -0,0 +1,193 @@ +# $Id$ +# +# This Makefile controls a build process of the pam_unix module +# for Linux-PAM. You should not modify this Makefile. +# +# rewritten to compile new module Andrew Morgan +# 1996/11/6 +# + +# +# Note, the STATIC module is commented out because it doesn't work. +# please fix! +# + +ifndef FULL_LINUX_PAM_SOURCE_TREE +export DYNAMIC=-DPAM_DYNAMIC +export CC=gcc +export CFLAGS=-O2 -Dlinux -DLINUX_PAM \ + -ansi -D_POSIX_SOURCE -Wall -Wwrite-strings \ + -Wpointer-arith -Wcast-qual -Wcast-align -Wtraditional \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Winline \ + -Wshadow -pedantic -fPIC +export MKDIR=mkdir -p +export LD_D=gcc -shared -Xlinker -x +export HAVE_PWDBLIB=yes +endif + +ifeq ($(shell if [ -f /lib/libcrypt.so.* ]; then echo yes ; else echo no ; fi),yes) +EXTRALS += -lcrypt +endif + +ifeq ($(HAVE_PWDBLIB),yes) + +TITLE=pam_pwdb +CHKPWD=pwdb_chkpwd + +# compilation flags +EXTRAS= +# extra object files +PLUS= +# extra files that may be needed to be created +CREATE= + +# NOTE: this module links dynamically to the libpwdb library. +EXTRALS += -lpwdb +EXTRAS += -DCHKPWD_HELPER=\"$(SUPLEMENTED)/$(CHKPWD)\" + +########################### don't edit below ########################## + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +#LIBOBJS = $(addprefix static/,$(LIBOBJ)) +LIBDEPS = pam_unix_acct.-c pam_unix_auth.-c pam_unix_passwd.-c \ + pam_unix_sess.-c pam_unix_pwupd.-c support.-c bigcrypt.-c + +PLUS += md5_good.o md5_broken.o md5_crypt_good.o md5_crypt_broken.o +CFLAGS += $(EXTRAS) + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +#ifdef STATIC +#LIBSTATIC = lib$(TITLE).o +#endif + +all: info dirs $(PLUS) $(LIBSHARED) $(LIBSTATIC) register $(CHKPWD) + +dynamic/$(LIBOBJ) : $(LIBSRC) $(LIBDEPS) + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +#static/$(LIBOBJ) : $(LIBSRC) $(LIBDEPS) +# $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +info: + @echo + @echo "*** Building PAM_pwdb module..." + @echo + +$(CHKPWD): pwdb_chkpwd.o md5_good.o md5_broken.o \ + md5_crypt_good.o md5_crypt_broken.o + $(CC) -o $(CHKPWD) $^ -lpwdb + +pwdb_chkpwd.o: pwdb_chkpwd.c pam_unix_md.-c bigcrypt.-c + +md5_good.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -DHIGHFIRST -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_broken.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_good.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_broken.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +dirs: +ifdef DYNAMIC + @$(MKDIR) ./dynamic +endif +#ifdef STATIC +# @$(MKDIR) ./static +#endif + +register: +#ifdef STATIC +# ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +#endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(PLUS) $(EXTRALS) +endif + +#ifdef STATIC +#$(LIBOBJS): $(LIBSRC) +# +#$(LIBSTATIC): $(LIBOBJS) +# $(LD) -r -o $@ $(LIBOBJS) $(PLUS) $(EXTRALS) +#endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SUPLEMENTED) + $(INSTALL) -m 4555 -o root -g root $(CHKPWD) $(FAKEROOT)$(SUPLEMENTED) + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(SUPLEMENTED)/$(CHKPWD) + +clean: + rm -f $(CHKPWD) $(LIBOBJD) $(LIBOBJS) $(MOREDELS) core *~ *.o *.so + +extraclean: clean + rm -f *.a *.o *.so *.bak + +else + +include ../dont_makefile + +endif + +##################################################################### +# $Log$ +# Revision 1.1 2000/06/20 22:11:47 agmorgan +# Initial revision +# +# Revision 1.4 1999/08/01 16:18:27 morgan +# added a conditional for libcrypt +# +# Revision 1.3 1999/07/08 05:02:02 morgan +# glibc fixes (Thorsten Kukuk, Adam J. Richter) +# +# Revision 1.2 1999/07/04 23:22:38 morgan +# Andrey's MD5 (bigendian) work around + cleanup to address problems with +# applications that let an (ab)user kill them off without giving PAM the +# opportunity to end. [Problem report from Tani Hosokawa on bugtraq.] +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.7 1997/04/05 06:28:50 morgan +# fakeroot +# +# Revision 1.6 1997/02/15 17:25:32 morgan +# update for .56 . extra commands for new helper binary +# +# Revision 1.5 1997/01/04 20:39:08 morgan +# conditional on having libpwdb +# +# Revision 1.4 1996/12/01 03:02:03 morgan +# changed banner, removed linking libraries +# +# Revision 1.3 1996/11/10 20:14:42 morgan +# cross platform support +# +# Revision 1.2 1996/09/05 06:36:49 morgan +# options added and use of LD altered +# +# Revision 1.1 1996/08/29 13:23:29 morgan +# Initial revision +# +# diff --git a/modules/pam_pwdb/README b/modules/pam_pwdb/README new file mode 100644 index 00000000..4f420855 --- /dev/null +++ b/modules/pam_pwdb/README @@ -0,0 +1,41 @@ +This is the pam_unix module. It has been significantly rewritten since +.51 was released (due mostly to the efforts of Cristian Gafton), and +now takes more options and correctly updates vanilla UNIX/shadow/md5 +passwords. + +[Please read the source and make a note of all the warnings there, as +the license suggests -- use at your own risk.] + +So far as I am concerned this module is now pretty stable. If you find +any bugs, PLEASE tell me! + +Options recognized by this module are as follows: + + debug - log more debugging info + audit - a little more extreme than debug + use_first_pass - don't prompt the user for passwords + take them from PAM_ items instead + try_first_pass - don't prompt the user for the passwords + unless PAM_(OLD)AUTHTOK is unset + use_authtok - like try_first_pass, but *fail* if the new + PAM_AUTHTOK has not been previously set. + (intended for stacking password modules only) + not_set_pass - don't set the PAM_ items with the passwords + used by this module. + shadow - try to maintian a shadow based system. + unix - when changing passwords, they are placed + in the /etc/passwd file + md5 - when a user changes their password next, + encrypt it with the md5 algorithm. + bigcrypt - when a user changes their password next, + excrypt it with the DEC C2-algorithm(0). + nodelay - used to prevent failed authentication + resulting in a delay of about 1 second. + +There is some support for building a shadow file on-the-fly from an +/etc/passwd file. This is VERY alpha. If you want to play with it you +should read the source to find the appropriate #define that you will +need. + +--------------------- +Andrew Morgan diff --git a/modules/pam_pwdb/TODO b/modules/pam_pwdb/TODO new file mode 100644 index 00000000..9dc7fc7e --- /dev/null +++ b/modules/pam_pwdb/TODO @@ -0,0 +1,34 @@ +$Id$ + + * get NIS working + * .. including "nonis" argument + * add helper binary + +Wed Sep 4 23:40:09 PDT 1996 Andrew G. Morgan + + * verify that it works for everyone + * look more seriously at the issue of generating a shadow + system on the fly + * add some more password flavors + +Thu Aug 29 06:26:42 PDT 1996 Andrew G. Morgan + + * check that complete rewrite works! ;^) + * complete shadow support to the password changing code. + Also some code needed here for session managment? + (both pam.conf argument to turn it on/off, and some + conditional compilation.) + * md5 passwords... + * make the exclusive nature of the arguments work. That is, + only recognize the flags when appropriate. + +Wed May 8 19:08:49 EDT 1996 Alexander O. Yuriev + + * support.c should go. + +Tue Apr 23 21:43:55 EDT 1996 Alexander O. Yuriev + + * pam_sm_chauth_tok() should be written + * QUICK FIX: pam_sm_setcred() probably returns incorrect error code + + diff --git a/modules/pam_pwdb/bigcrypt.-c b/modules/pam_pwdb/bigcrypt.-c new file mode 100644 index 00000000..321f2491 --- /dev/null +++ b/modules/pam_pwdb/bigcrypt.-c @@ -0,0 +1,114 @@ +/* + * This function implements the "bigcrypt" algorithm specifically for + * Linux-PAM. + * + * This algorithm is algorithm 0 (default) shipped with the C2 secure + * implementation of Digital UNIX. + * + * Disclaimer: This work is not based on the source code to Digital + * UNIX, nor am I connected to Digital Equipment Corp, in any way + * other than as a customer. This code is based on published + * interfaces and reasonable guesswork. + * + * Description: The cleartext is divided into blocks of SEGMENT_SIZE=8 + * characters or less. Each block is encrypted using the standard UNIX + * libc crypt function. The result of the encryption for one block + * provides the salt for the suceeding block. + * + * Restrictions: The buffer used to hold the encrypted result is + * statically allocated. (see MAX_PASS_LEN below). This is necessary, + * as the returned pointer points to "static data that are overwritten + * by each call", (XPG3: XSI System Interface + Headers pg 109), and + * this is a drop in replacement for crypt(); + * + * Andy Phillips + */ + +/* + * Max cleartext password length in segments of 8 characters this + * function can deal with (16 segments of 8 chars= max 128 character + * password). + */ + +#define MAX_PASS_LEN 16 +#define SEGMENT_SIZE 8 +#define SALT_SIZE 2 +#define KEYBUF_SIZE ((MAX_PASS_LEN*SEGMENT_SIZE)+SALT_SIZE) +#define ESEGMENT_SIZE 11 +#define CBUF_SIZE ((MAX_PASS_LEN*ESEGMENT_SIZE)+SALT_SIZE+1) + +static char *bigcrypt(const char *key, const char *salt) +{ + static char dec_c2_cryptbuf[CBUF_SIZE]; /* static storage area */ + + unsigned long int keylen,n_seg,j; + char *cipher_ptr,*plaintext_ptr,*tmp_ptr,*salt_ptr; + char keybuf[KEYBUF_SIZE+1]; + + D(("called with key='%s', salt='%s'.", key, salt)); + + /* reset arrays */ + memset(keybuf, 0, KEYBUF_SIZE+1); + memset(dec_c2_cryptbuf, 0, CBUF_SIZE); + + /* fill KEYBUF_SIZE with key */ + strncpy(keybuf, key, KEYBUF_SIZE); + + /* deal with case that we are doing a password check for a + conventially encrypted password: the salt will be + SALT_SIZE+ESEGMENT_SIZE long. */ + if (strlen(salt) == (SALT_SIZE+ESEGMENT_SIZE)) + keybuf[SEGMENT_SIZE] = '\0'; /* terminate password early(?) */ + + keylen = strlen(keybuf); + + if (!keylen) { + n_seg = 1; + } else { + /* work out how many segments */ + n_seg = 1 + ((keylen-1)/SEGMENT_SIZE); + } + + if (n_seg > MAX_PASS_LEN) + n_seg = MAX_PASS_LEN; /* truncate at max length */ + + /* set up some pointers */ + cipher_ptr = dec_c2_cryptbuf; + plaintext_ptr = keybuf; + + /* do the first block with supplied salt */ + tmp_ptr = crypt(plaintext_ptr,salt); /* libc crypt() */ + + /* and place in the static area */ + strncpy(cipher_ptr, tmp_ptr, 13); + cipher_ptr += ESEGMENT_SIZE + SALT_SIZE; + plaintext_ptr += SEGMENT_SIZE; /* first block of SEGMENT_SIZE */ + + /* change the salt (1st 2 chars of previous block) - this was found + by dowsing */ + + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + + /* so far this is identical to "return crypt(key, salt);", if + there is more than one block encrypt them... */ + + if (n_seg > 1) { + for (j=2; j <= n_seg; j++) { + + tmp_ptr = crypt(plaintext_ptr, salt_ptr); + + /* skip the salt for seg!=0 */ + strncpy(cipher_ptr, (tmp_ptr+SALT_SIZE), ESEGMENT_SIZE); + + cipher_ptr += ESEGMENT_SIZE; + plaintext_ptr += SEGMENT_SIZE; + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + } + } + + D(("key=|%s|, salt=|%s|\nbuf=|%s|\n", key, salt, dec_c2_cryptbuf)); + + /* this is the terminated encrypted password */ + + return dec_c2_cryptbuf; +} diff --git a/modules/pam_pwdb/md5.c b/modules/pam_pwdb/md5.c new file mode 100644 index 00000000..d3f47763 --- /dev/null +++ b/modules/pam_pwdb/md5.c @@ -0,0 +1,270 @@ +/* $Id$ + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:48 agmorgan + * Initial revision + * + * Revision 1.2 1999/07/04 23:22:38 morgan + * Andrey's MD5 (bigendian) work around + cleanup to address problems with + * applications that let an (ab)user kill them off without giving PAM the + * opportunity to end. [Problem report from Tani Hosokawa on bugtraq.] + * + * Revision 1.1.1.1 1998/07/12 05:17:17 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.1 1996/09/05 06:43:31 morgan + * Initial revision + * + */ + +#include +#include "md5.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32 t; + do { + t = (uint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32 *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Name(MD5Init)(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301U; + ctx->buf[1] = 0xefcdab89U; + ctx->buf[2] = 0x98badcfeU; + ctx->buf[3] = 0x10325476U; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Name(MD5Update)(struct MD5Context *ctx, unsigned const char *buf, unsigned len) +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Name(MD5Final)(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *) ctx->in)[14] = ctx->bits[0]; + ((uint32 *) ctx->in)[15] = ctx->bits[1]; + + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Name(MD5Transform)(uint32 buf[4], uint32 const in[16]) +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478U, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756U, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbU, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeU, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafU, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aU, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613U, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501U, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8U, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7afU, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1U, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beU, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122U, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193U, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eU, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821U, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562U, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340U, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51U, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaU, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dU, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453U, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681U, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8U, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6U, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6U, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87U, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edU, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905U, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8U, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9U, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aU, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942U, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681U, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122U, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cU, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44U, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9U, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60U, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70U, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6U, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faU, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085U, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05U, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039U, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5U, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8U, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665U, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244U, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97U, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7U, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039U, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3U, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92U, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dU, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1U, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fU, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0U, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314U, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1U, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82U, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235U, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbU, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391U, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/modules/pam_pwdb/md5.h b/modules/pam_pwdb/md5.h new file mode 100644 index 00000000..279ce46f --- /dev/null +++ b/modules/pam_pwdb/md5.h @@ -0,0 +1,34 @@ +#ifndef MD5_H +#define MD5_H + +#ifdef __alpha +typedef unsigned int uint32; +#else +typedef unsigned long uint32; +#endif + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +void GoodMD5Init(struct MD5Context *); +void GoodMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void GoodMD5Final(unsigned char digest[16], struct MD5Context *); +void GoodMD5Transform(uint32 buf[4], uint32 const in[16]); +void BrokenMD5Init(struct MD5Context *); +void BrokenMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void BrokenMD5Final(unsigned char digest[16], struct MD5Context *); +void BrokenMD5Transform(uint32 buf[4], uint32 const in[16]); + +char *Goodcrypt_md5(const char *pw, const char *salt); +char *Brokencrypt_md5(const char *pw, const char *salt); + +/* +* This is needed to make RSAREF happy on some MS-DOS compilers. +*/ + +typedef struct MD5Context MD5_CTX; + +#endif /* MD5_H */ diff --git a/modules/pam_pwdb/md5_crypt.c b/modules/pam_pwdb/md5_crypt.c new file mode 100644 index 00000000..1d755a08 --- /dev/null +++ b/modules/pam_pwdb/md5_crypt.c @@ -0,0 +1,153 @@ +/* $Id$ + * + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * Origin: Id: crypt.c,v 1.3 1995/05/30 05:42:22 rgrimes Exp + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:48 agmorgan + * Initial revision + * + * Revision 1.2 1999/07/04 23:22:38 morgan + * Andrey's MD5 (bigendian) work around + cleanup to address problems with + * applications that let an (ab)user kill them off without giving PAM the + * opportunity to end. [Problem report from Tani Hosokawa on bugtraq.] + * + * Revision 1.1.1.1 1998/07/12 05:17:17 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.1 1996/09/05 06:43:31 morgan + * Initial revision + * + */ + +#include +#include "md5.h" + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void +to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v&0x3f]; + v >>= 6; + } +} + +/* + * UNIX password + * + * Use MD5 for what it is best at... + */ + +char * MD5Name(crypt_md5)(const char *pw, const char *salt) +{ + const char *magic = "$1$"; + /* This string is magic for this algorithm. Having + * it this way, we can get get better later on */ + static char passwd[120], *p; + static const char *sp,*ep; + unsigned char final[16]; + int sl,pl,i,j; + MD5_CTX ctx,ctx1; + unsigned long l; + + /* Refine the Salt first */ + sp = salt; + + /* If it starts with the magic string, then skip that */ + if(!strncmp(sp,magic,strlen(magic))) + sp += strlen(magic); + + /* It stops at the first '$', max 8 chars */ + for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++) + continue; + + /* get the length of the true salt */ + sl = ep - sp; + + MD5Name(MD5Init)(&ctx); + + /* The password first, since that is what is most unknown */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)pw,strlen(pw)); + + /* Then our magic string */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)magic,strlen(magic)); + + /* Then the raw salt */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)sp,sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + MD5Name(MD5Init)(&ctx1); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + for(pl = strlen(pw); pl > 0; pl -= 16) + MD5Name(MD5Update)(&ctx,(unsigned const char *)final,pl>16 ? 16 : pl); + + /* Don't leave anything around in vm they could use. */ + memset(final,0,sizeof final); + + /* Then something really weird... */ + for (j=0,i = strlen(pw); i ; i >>= 1) + if(i&1) + MD5Name(MD5Update)(&ctx, (unsigned const char *)final+j, 1); + else + MD5Name(MD5Update)(&ctx, (unsigned const char *)pw+j, 1); + + /* Now make the output string */ + strcpy(passwd,magic); + strncat(passwd,sp,sl); + strcat(passwd,"$"); + + MD5Name(MD5Final)(final,&ctx); + + /* + * and now, just to make sure things don't run too fast + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for(i=0;i<1000;i++) { + MD5Name(MD5Init)(&ctx1); + if(i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + + if(i % 3) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + + if(i % 7) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + + if(i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + } + + p = passwd + strlen(passwd); + + l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4; + l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4; + l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4; + l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4; + l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4; + l = final[11] ; to64(p,l,2); p += 2; + *p = '\0'; + + /* Don't leave anything around in vm they could use. */ + memset(final,0,sizeof final); + + return passwd; +} + diff --git a/modules/pam_pwdb/pam_pwdb.c b/modules/pam_pwdb/pam_pwdb.c new file mode 100644 index 00000000..68ca68fd --- /dev/null +++ b/modules/pam_pwdb/pam_pwdb.c @@ -0,0 +1,256 @@ +/* + * $Id$ + * + * This is the single file that will be compiled for pam_unix. + * it includes each of the modules that have beed defined in the .-c + * files in this directory. + * + * It is a little ugly to do it this way, but it is a simple way of + * defining static functions only once, and yet keeping the separate + * files modular. If you can think of something better, please email + * Andrew Morgan + * + * See the end of this file for Copyright information. + */ + +static const char rcsid[] = +"$Id$\n" +" - PWDB Pluggable Authentication module. " +; + +/* #define DEBUG */ + +#define _SVID_SOURCE +#define _BSD_SOURCE +#define _BSD_COMPAT + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include /* for time() */ +#include +#include + +#include +#include + +#include + +/* indicate the following groups are defined */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD + +#include +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +#include "./support.-c" + +/* + * PAM framework looks for these entry-points to pass control to the + * authentication module. + */ + +#include "./pam_unix_auth.-c" + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_auth( pamh, ctrl ); + pwdb_end(); + + if ( on(UNIX_LIKE_AUTH, ctrl) ) { + D(("recording return code for next time [%d]", retval)); + pam_set_data(pamh, "pwdb_setcred_return", (void *) &retval, NULL); + } + + D(("done. [%s]", pam_strerror(pamh, retval))); + + return retval; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags + , int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_set_credentials(pamh, ctrl); + pwdb_end(); + + if ( on(UNIX_LIKE_AUTH, ctrl) ) { + int *pretval = &retval; + + D(("recovering return code from auth call")); + pam_get_data(pamh, "pwdb_setcred_return", (const void **) &pretval); + pam_set_data(pamh, "pwdb_setcred_return", NULL, NULL); + D(("recovered data indicates that old retval was %d", retval)); + } + + return retval; +} + +/* + * PAM framework looks for these entry-points to pass control to the + * account management module. + */ + +#include "./pam_unix_acct.-c" + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_acct_mgmt(pamh, ctrl); + pwdb_end(); + + D(("done.")); + + return retval; +} + +/* + * PAM framework looks for these entry-points to pass control to the + * session module. + */ + +#include "./pam_unix_sess.-c" + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_open_session(pamh, ctrl); + pwdb_end(); + + return retval; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_close_session(pamh, ctrl); + pwdb_end(); + + return retval; +} + +/* + * PAM framework looks for these entry-points to pass control to the + * password changing module. + */ + +#include "./pam_unix_passwd.-c" + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + pwdb_start(); + ctrl = set_ctrl(flags, argc, argv); + retval = _unix_chauthtok(pamh, ctrl); + pwdb_end(); + + D(("done.")); + + return retval; +} + +/* static module data */ + +#ifdef PAM_STATIC +struct pam_module _pam_pwdb_modstruct = { + "pam_pwdb", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif + +/* + * Copyright (c) Andrew G. Morgan, 1996. All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/pam_unix_acct.-c b/modules/pam_pwdb/pam_unix_acct.-c new file mode 100644 index 00000000..16869054 --- /dev/null +++ b/modules/pam_pwdb/pam_unix_acct.-c @@ -0,0 +1,298 @@ +/* + * $Id$ + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:49 agmorgan + * Initial revision + * + * Revision 1.1.1.1 1998/07/12 05:17:17 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.6 1997/01/04 20:37:15 morgan + * extra debugging + * + * Revision 1.5 1996/12/01 03:05:54 morgan + * debugging with _pam_macros.h + * + * Revision 1.4 1996/11/10 21:03:57 morgan + * pwdb conversion + * + * Revision 1.3 1996/09/05 06:45:45 morgan + * tidied shadow acct management + * + * Revision 1.2 1996/09/01 01:13:14 morgan + * Cristian Gafton's patches + * + * Revision 1.1 1996/08/29 13:27:51 morgan + * Initial revision + * + * + * See end of file for copyright information + */ + +static const char rcsid_acct[] = +"$Id$\n" +" - PAM_PWDB account management "; + +/* the shadow suite has accout managment.. */ + +static int _shadow_acct_mgmt_exp(pam_handle_t *pamh, unsigned int ctrl, + const struct pwdb *pw, const char *uname) +{ + const struct pwdb_entry *pwe = NULL; + time_t curdays; + int last_change, max_change; + int retval; + + D(("called.")); + + /* Now start the checks */ + + curdays = time(NULL)/(60*60*24); /* today */ + + /* First: has account expired ? (CG) + * - expire < curdays + * - or (last_change + max_change + defer_change) < curdays + * - in both cases, deny access + */ + + D(("pwdb_get_entry")); + retval = pwdb_get_entry(pw, "expire", &pwe); + if (retval == PWDB_SUCCESS) { + int expire; + + expire = *( (const int *) pwe->value ); + (void) pwdb_entry_delete(&pwe); /* no longer needed */ + + if ((curdays > expire) && (expire > 0)) { + + _log_err(LOG_NOTICE + , "acct: account %s has expired (account expired)" + , uname); + make_remark(pamh, ctrl, PAM_ERROR_MSG + , "Your account has expired; " + "please contact your system administrator"); + + D(("account expired")); + return PAM_ACCT_EXPIRED; + } + } + + D(("pwdb_get_entry")); + retval = pwdb_get_entry(pw, "last_change", &pwe); + if ( retval == PWDB_SUCCESS ) { + last_change = *( (const int *) pwe->value ); + } else { + last_change = curdays; + } + (void) pwdb_entry_delete(&pwe); + + D(("pwdb_get_entry")); + retval = pwdb_get_entry(pw, "max_change", &pwe); + if ( retval == PWDB_SUCCESS ) { + max_change = *( (const int *) pwe->value ); + } else { + max_change = -1; + } + (void) pwdb_entry_delete(&pwe); + + D(("pwdb_get_entry")); + retval = pwdb_get_entry(pw, "defer_change", &pwe); + if (retval == PWDB_SUCCESS) { + int defer_change; + + defer_change = *( (const int *) pwe->value ); + (void) pwdb_entry_delete(&pwe); + + if ((curdays > (last_change + max_change + defer_change)) + && (max_change != -1) && (defer_change != -1) + && (last_change > 0)) { + + if ( on(UNIX_DEBUG, ctrl) ) { + _log_err(LOG_NOTICE, "acct: account %s has expired " + "(failed to change password)", uname); + } + make_remark(pamh, ctrl, PAM_ERROR_MSG + , "Your password has expired; " + "please see your system administrator"); + + D(("account expired2")); + return PAM_ACCT_EXPIRED; + } + } + + /* Now test if the password is expired, but the user still can + * change their password. (CG) + * - last_change = 0 + * - last_change + max_change < curdays + */ + + D(("when was the last change")); + if (last_change == 0) { + + if ( on(UNIX_DEBUG, ctrl) ) { + _log_err(LOG_NOTICE + , "acct: expired password for user %s (root enforced)" + , uname); + } + make_remark(pamh, ctrl, PAM_ERROR_MSG + , "You are required to change your password immediately" + ); + + D(("need a new password")); + return PAM_NEW_AUTHTOK_REQD; + } + + if (((last_change + max_change) < curdays) && + (max_change < 99999) && (max_change > 0)) { + + if ( on(UNIX_DEBUG, ctrl) ) { + _log_err(LOG_DEBUG + , "acct: expired password for user %s (password aged)" + , uname); + } + make_remark(pamh, ctrl, PAM_ERROR_MSG + , "Your password has expired; please change it!"); + + D(("need a new password 2")); + return PAM_NEW_AUTHTOK_REQD; + } + + /* + * Now test if the password is about to expire (CG) + * - last_change + max_change - curdays <= warn_change + */ + + retval = pwdb_get_entry(pw, "warn_change", &pwe); + if ( retval == PWDB_SUCCESS ) { + int warn_days, daysleft; + + daysleft = last_change + max_change - curdays; + warn_days = *((const int *) pwe->value); + (void) pwdb_entry_delete(&pwe); + + if ((daysleft <= warn_days) && (warn_days > 0)) { + char *s; + + if ( on(UNIX_DEBUG, ctrl) ) { + _log_err(LOG_DEBUG + , "acct: password for user %s will expire in %d days" + , uname, daysleft); + } + +#define LocalComment "Warning: your password will expire in %d day%s" + if ((s = (char *) malloc(30+sizeof(LocalComment))) == NULL) { + _log_err(LOG_CRIT, "malloc failure in " __FILE__); + retval = PAM_BUF_ERR; + } else { + + sprintf(s, LocalComment, daysleft, daysleft == 1 ? "":"s"); + + make_remark(pamh, ctrl, PAM_TEXT_INFO, s); + free(s); + } +#undef LocalComment + } + } else { + retval = PAM_SUCCESS; + } + + D(("all done")); + return retval; +} + + +/* + * this function checks for the account details. The user may not be + * permitted to log in at this time etc.. Within the context of + * vanilla Unix, this function simply does nothing. The shadow suite + * added password/account expiry, but PWDB takes care of this + * transparently. + */ + +static int _unix_acct_mgmt(pam_handle_t *pamh, unsigned int ctrl) +{ + const struct pwdb *pw = NULL; + + char *uname=NULL; + int retval; + + D(("called.")); + + /* identify user */ + + retval = pam_get_item(pamh,PAM_USER,(const void **)&uname); + D(("user = `%s'", uname)); + if (retval != PAM_SUCCESS || uname == NULL) { + _log_err(LOG_ALERT + , "acct; could not identify user (from uid=%d)" + , getuid()); + return PAM_USER_UNKNOWN; + } + + /* get database information for user */ + + retval = pwdb_locate("user", PWDB_DEFAULT, uname, PWDB_ID_UNKNOWN, &pw); + if (retval != PWDB_SUCCESS || pw == NULL) { + + _log_err(LOG_ALERT, "acct; %s (%s from uid=%d)" + , pwdb_strerror(retval), uname, getuid()); + if ( pw ) { + (void) pwdb_delete(&pw); + } + return PAM_USER_UNKNOWN; + } + + /* now check the user's times etc.. */ + + retval = _shadow_acct_mgmt_exp(pamh, ctrl, pw, uname); + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "expiry check failed for '%s'", uname); + } + + /* Done with pw */ + + (void) pwdb_delete(&pw); + + /* all done */ + + D(("done.")); + return retval; +} + +/* + * Copyright (c) Elliot Lee, 1996. + * Copyright (c) Andrew Morgan 1996. + * Copyright (c) Cristian Gafton 1996. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/pam_unix_auth.-c b/modules/pam_pwdb/pam_unix_auth.-c new file mode 100644 index 00000000..e4dfe136 --- /dev/null +++ b/modules/pam_pwdb/pam_unix_auth.-c @@ -0,0 +1,131 @@ +/* + * $Id$ + * + * See end of file for Copyright information. + */ + +static const char rcsid_auth[] = +"$Id$: pam_unix_auth.-c,v 1.2 1996/09/05 06:46:53 morgan Exp morgan $\n" +" - PAM_PWDB authentication functions. "; + +/* + * _unix_auth() is a front-end for UNIX/shadow authentication + * + * First, obtain the password from the user. Then use a + * routine in 'support.-c' to authenticate the user. + */ + +#define _UNIX_AUTHTOK "-UN*X-PASS" + +static int _unix_auth(pam_handle_t *pamh, unsigned int ctrl) +{ + int retval; + const char *name, *p; + + D(("called.")); + + /* get the user'name' */ + + retval = _unix_get_user(pamh, ctrl, NULL, &name); + if (retval != PAM_SUCCESS ) { + if (retval != PAM_CONV_AGAIN) { + if ( on(UNIX_DEBUG,ctrl) ) { + _log_err(LOG_DEBUG, "auth could not identify user"); + } + } else { + D(("pam_get_user/conv() function is not ready yet")); + /* it is safe to resume this function so we translate this + retval to the value that indicates we're happy to resume. */ + retval = PAM_INCOMPLETE; + } + return retval; + } + + /* if this user does not have a password... */ + + if ( _unix_blankpasswd(ctrl, name) ) { + D(("user '%s' has blank passwd", name)); + name = NULL; + return PAM_SUCCESS; + } + + /* get this user's authentication token */ + + retval = _unix_read_password(pamh, ctrl, NULL, "Password: ", NULL + , _UNIX_AUTHTOK, &p); + if (retval != PAM_SUCCESS) { + if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_CRIT, "auth could not identify password for [%s]" + , name); + } else { + D(("conversation function is not ready yet")); + /* it is safe to resume this function so we translate this + retval to the value that indicates we're happy to resume. */ + retval = PAM_INCOMPLETE; + } + name = NULL; + return retval; + } + D(("user=%s, password=[%s]", name, p)); + + /* verify the password of this user */ + retval = _unix_verify_password(pamh, name, p, ctrl); + name = p = NULL; + + D(("done [%d]", retval)); + + return retval; +} + +/* + * This function is for setting unix credentials. Sun has indicated + * that there are *NO* authentication credentials for unix. The + * obvious credentials would be the group membership of the user as + * listed in the /etc/group file. However, Sun indicates that it is + * the responsibility of the application to set these. + */ + +static int _unix_set_credentials(pam_handle_t *pamh, unsigned int ctrl) +{ + D(("called returning.")); + + return PAM_SUCCESS; +} + +/******************************************************************** + * Copyright (c) Alexander O. Yuriev, 1996. + * Copyright (c) Andrew G. Morgan 1996 + * Copyright (c) Cristian Gafton 1996, 1997 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/modules/pam_pwdb/pam_unix_md.-c b/modules/pam_pwdb/pam_unix_md.-c new file mode 100644 index 00000000..d9b2c51b --- /dev/null +++ b/modules/pam_pwdb/pam_unix_md.-c @@ -0,0 +1,73 @@ +/* + * This function is a front-end for the message digest algorithms used + * to compute the user's encrypted passwords. No reversible encryption + * is used here and I intend to keep it that way. + * + * While there are many sources of encryption outside the United + * States, it *may* be illegal to re-export reversible encryption + * computer code. Until such time as it is legal to export encryption + * software freely from the US, please do not send me any. (AGM) + */ + +/* this should have been defined in a header file.. Why wasn't it? AGM */ +extern char *crypt(const char *key, const char *salt); + +#include "md5.h" +#include "bigcrypt.-c" + +struct cfns { + const char *salt; + int len; + char * (* mdfn)(const char *key, const char *salt); +}; + +/* array of non-standard digest algorithms available */ + +#define N_MDS 1 +const static struct cfns cfn_list[N_MDS] = { + { "$1$", 3, Goodcrypt_md5 }, +}; + +static char *_pam_md(const char *key, const char *salt) +{ + char *x,*e=NULL; + int i; + + D(("called with key='%s', salt='%s'", key, salt)); + + /* check for non-standard salts */ + + for (i=0; i= N_MDS ) { + e = bigcrypt(key, salt); /* (defaults to standard algorithm) */ + } + + x = x_strdup(e); /* put e in malloc()ed memory */ + _pam_overwrite(e); /* clean up */ + return x; /* this must be deleted elsewhere */ +} + +#ifndef PWDB_NO_MD_COMPAT +static char *_pam_md_compat(const char *key, const char *salt) +{ + char *x,*e=NULL; + + D(("called with key='%s', salt='%s'", key, salt)); + + if ( !strncmp("$1$", salt, 3) ) { + e = Brokencrypt_md5(key, salt); + x = x_strdup(e); /* put e in malloc()ed memory */ + _pam_overwrite(e); /* clean up */ + } else { + x = x_strdup("*"); + } + + return x; /* this must be deleted elsewhere */ +} +#endif /* PWDB_NO_MD_COMPAT */ diff --git a/modules/pam_pwdb/pam_unix_passwd.-c b/modules/pam_pwdb/pam_unix_passwd.-c new file mode 100644 index 00000000..13801ba3 --- /dev/null +++ b/modules/pam_pwdb/pam_unix_passwd.-c @@ -0,0 +1,404 @@ +/* $Id$ */ + +/* + * $Log$ + * Revision 1.1 2000/06/20 22:11:50 agmorgan + * Initial revision + * + * Revision 1.2 1999/07/04 23:22:38 morgan + * Andrey's MD5 (bigendian) work around + cleanup to address problems with + * applications that let an (ab)user kill them off without giving PAM the + * opportunity to end. [Problem report from Tani Hosokawa on bugtraq.] + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.6 1997/04/05 06:31:06 morgan + * mostly a reformat. + * + * Revision 1.5 1996/12/01 03:05:54 morgan + * debugging with _pam_macros.h + * + * Revision 1.4 1996/11/10 21:04:51 morgan + * pwdb conversion + * + * Revision 1.3 1996/09/05 06:48:15 morgan + * A lot has changed. I'd recommend you study the diff. + * + * Revision 1.2 1996/09/01 16:33:27 morgan + * Cristian Gafton's changes + * + * Revision 1.1 1996/08/29 13:21:27 morgan + * Initial revision + * + */ + +static const char rcsid_pass[] = +"$Id$\n" +" - PAM_PWDB password module " +; + +#include "pam_unix_pwupd.-c" + +/* passwd/salt conversion macros */ + +#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.') +#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') + +/* data tokens */ + +#define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS" +#define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS" + +/* Implementation */ + +/* + * i64c - convert an integer to a radix 64 character + */ +static int i64c(int i) +{ + if (i < 0) + return ('.'); + else if (i > 63) + return ('z'); + if (i == 0) + return ('.'); + if (i == 1) + return ('/'); + if (i >= 2 && i <= 11) + return ('0' - 2 + i); + if (i >= 12 && i <= 37) + return ('A' - 12 + i); + if (i >= 38 && i <= 63) + return ('a' - 38 + i); + return ('\0'); +} + +/* + * FUNCTION: _pam_unix_chauthtok() + * + * this function works in two passes. The first, when UNIX__PRELIM is + * set, obtains the previous password. It sets the PAM_OLDAUTHTOK item + * or stores it as a data item. The second function obtains a new + * password (verifying if necessary, that the user types it the same a + * second time.) depending on the 'ctrl' flags this new password may + * be stored in the PAM_AUTHTOK item or a private data item. + * + * Having obtained a new password. The function updates the + * /etc/passwd (and optionally the /etc/shadow) file(s). + * + * Provision is made for the creation of a blank shadow file if none + * is available, but one is required to update the shadow file -- the + * intention being for shadow passwords to be seamlessly implemented + * from the generic UNIX scheme. -- THIS BIT IS PRE-ALPHA.. and included + * in this release (.52) mostly for the purpose of discussion. + */ + +static int _unix_chauthtok(pam_handle_t *pamh, unsigned int ctrl) +{ + int retval; + unsigned int lctrl; + + /* */ + const char *user; + const char *pass_old, *pass_new; + /* */ + + D(("called")); + + /* + * First get the name of a user + */ + + retval = _unix_get_user( pamh, ctrl, "Username: ", &user ); + if ( retval != PAM_SUCCESS ) { + if ( on(UNIX_DEBUG,ctrl) ) { + _log_err(LOG_DEBUG, "password - could not identify user"); + } + return retval; + } + + if ( on(UNIX__PRELIM, ctrl) ) { + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + + char *Announce; + + D(("prelim check")); + + if ( _unix_blankpasswd(ctrl, user) ) { + + return PAM_SUCCESS; + + } else if ( off(UNIX__IAMROOT, ctrl) ) { + + /* instruct user what is happening */ +#define greeting "Changing password for " + Announce = (char *) malloc(sizeof(greeting)+strlen(user)); + if (Announce == NULL) { + _log_err(LOG_CRIT, "password - out of memory"); + return PAM_BUF_ERR; + } + (void) strcpy(Announce, greeting); + (void) strcpy(Announce+sizeof(greeting)-1, user); +#undef greeting + + lctrl = ctrl; + set(UNIX__OLD_PASSWD, lctrl); + retval = _unix_read_password( pamh, lctrl + , Announce + , "(current) UNIX password: " + , NULL + , _UNIX_OLD_AUTHTOK + , &pass_old ); + free(Announce); + + if ( retval != PAM_SUCCESS ) { + _log_err(LOG_NOTICE + , "password - (old) token not obtained"); + return retval; + } + + /* verify that this is the password for this user */ + + retval = _unix_verify_password(pamh, user, pass_old, ctrl); + } else { + D(("process run by root so do nothing this time around")); + pass_old = NULL; + retval = PAM_SUCCESS; /* root doesn't have too */ + } + + if ( retval != PAM_SUCCESS ) { + D(("Authentication failed")); + pass_old = NULL; + return retval; + } + + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); + pass_old = NULL; + if ( retval != PAM_SUCCESS ) { + _log_err(LOG_CRIT, "failed to set PAM_OLDAUTHTOK"); + } + + } else if ( on( UNIX__UPDATE, ctrl ) ) { + /* tpass is used below to store the _pam_md() return; it + * should be _pam_delete()'d. */ + + char *tpass=NULL; + + /* + * obtain the proposed password + */ + + D(("do update")); + + /* + * get the old token back. NULL was ok only if root [at this + * point we assume that this has already been enforced on a + * previous call to this function]. + */ + + if ( off(UNIX_NOT_SET_PASS, ctrl) ) { + retval = pam_get_item(pamh, PAM_OLDAUTHTOK + , (const void **)&pass_old); + } else { + retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK + , (const void **)&pass_old); + if (retval == PAM_NO_MODULE_DATA) { + retval = PAM_SUCCESS; + pass_old = NULL; + } + } + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "user not authenticated"); + return retval; + } + + D(("get new password now")); + + lctrl = ctrl; + + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + if ( on(UNIX_USE_AUTHTOK, lctrl) ) { + set(UNIX_USE_FIRST_PASS, lctrl); + } + + retval = _unix_read_password( pamh, lctrl + , NULL + , "Enter new UNIX password: " + , "Retype new UNIX password: " + , _UNIX_NEW_AUTHTOK + , &pass_new ); + + if ( retval != PAM_SUCCESS ) { + if ( on(UNIX_DEBUG,ctrl) ) { + _log_err(LOG_ALERT + , "password - new password not obtained"); + } + pass_old = NULL; /* tidy up */ + return retval; + } + + D(("returned to _unix_chauthtok")); + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (pass_new[0] == '\0') { /* "\0" password = NULL */ + pass_new = NULL; + } + + retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new); + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "new password not acceptable"); + pass_new = pass_old = NULL; /* tidy up */ + return retval; + } + + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + /* + * First we encrypt the new password. + * + * XXX - this is where we might need some code for RADIUS types + * of password handling... no encryption needed.. + */ + + if ( on(UNIX_MD5_PASS, ctrl) ) { + + /* + * Code lifted from Marek Michalkiewicz's shadow suite. (CG) + * removed use of static variables (AGM) + */ + + struct timeval tv; + MD5_CTX ctx; + unsigned char result[16]; + char *cp = (char *)result; + unsigned char tmp[16]; + int i; + + GoodMD5Init(&ctx); + gettimeofday(&tv, (struct timezone *) 0); + GoodMD5Update(&ctx, (void *) &tv, sizeof tv); + i = getpid(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + i = clock(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + GoodMD5Update(&ctx, result, sizeof result); + GoodMD5Final(tmp, &ctx); + strcpy(cp, "$1$"); /* magic for the MD5 */ + cp += strlen(cp); + for (i = 0; i < 8; i++) + *cp++ = i64c(tmp[i] & 077); + *cp = '\0'; + + /* no longer need cleartext */ + pass_new = tpass = _pam_md(pass_new, (const char *)result); + + } else { + /* + * Salt manipulation is stolen from Rick Faith's passwd + * program. Sorry Rick :) -- alex + */ + + time_t tm; + char salt[3]; + + time(&tm); + salt[0] = bin_to_ascii(tm & 0x3f); + salt[1] = bin_to_ascii((tm >> 6) & 0x3f); + salt[2] = '\0'; + + if ( off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8 ) { + /* to avoid using the _extensions_ of the bigcrypt() + function we truncate the newly entered password */ + char *temp = malloc(9); + + if (temp == NULL) { + _log_err(LOG_CRIT, "out of memory for password"); + pass_new = pass_old = NULL; /* tidy up */ + return PAM_BUF_ERR; + } + + /* copy first 8 bytes of password */ + strncpy(temp, pass_new, 8); + temp[8] = '\0'; + + /* no longer need cleartext */ + pass_new = tpass = _pam_md( temp, salt ); + + _pam_delete(temp); /* tidy up */ + } else { + /* no longer need cleartext */ + pass_new = tpass = _pam_md( pass_new, salt ); + } + } + + D(("password processed")); + + /* update the password database(s) -- race conditions..? */ + + retval = unix_update_db(pamh, ctrl, user, pass_old, pass_new); + pass_old = pass_new = NULL; + + } else { /* something has broken with the module */ + + _log_err(LOG_ALERT, "password received unknown request"); + retval = PAM_ABORT; + + } + + return retval; +} + +/* ****************************************************************** + * Copyright (c) Alexander O. Yuriev (alex@bach.cis.temple.edu), 1996. + * Copyright (c) Andrew Morgan 1996, 1997. + * Copyright (c) Cristian Gafton, 1996, 1997. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/pam_unix_pwupd.-c b/modules/pam_pwdb/pam_unix_pwupd.-c new file mode 100644 index 00000000..0f646369 --- /dev/null +++ b/modules/pam_pwdb/pam_unix_pwupd.-c @@ -0,0 +1,260 @@ +/* + * $Id$ + * + * This file contains the routines to update the passwd databases. + */ + +/* Implementation */ + +static int unix_update_db(pam_handle_t *pamh, int ctrl, const char *user, + const char *pass_old, const char *pass_new) +{ + const struct pwdb *pw=NULL; + const struct pwdb_entry *pwe=NULL; + pwdb_flag flag; + int retval, i; + + D(("called.")); + + /* obtain default user record */ + + retval = pwdb_locate("user", PWDB_DEFAULT, user, PWDB_ID_UNKNOWN, &pw); + if (retval == PWDB_PASS_PHRASE_REQD) { + retval = pwdb_set_entry(pw, "pass_phrase" + , pass_old, 1+strlen(pass_old) + , NULL, NULL, 0); + if (retval == PWDB_SUCCESS) + retval = pwdb_locate("user", pw->source, user + , PWDB_ID_UNKNOWN, &pw); + } + pass_old = NULL; + + if ( retval != PWDB_SUCCESS ) { + _log_err(LOG_ALERT, "cannot identify user %s (uid=%d)" + , user, getuid() ); + pass_new = NULL; + if (pw) + (void) pwdb_delete(&pw); + return PAM_USER_UNKNOWN; + } + + /* check that we can update all of the default databases */ + + retval = pwdb_flags("user", pw->source, &flag); + + if ( retval != PWDB_SUCCESS || ( pwdb_on(flag,PWDB_F_NOUPDATE) ) ) { + _log_err(LOG_ERR, "cannot update default database for user %s" + , user ); + pass_new = NULL; + if (pw) + (void) pwdb_delete(&pw); + return PAM_PERM_DENIED; + } + + /* If there was one, we delete the "last_change" entry */ + retval = pwdb_get_entry(pw, "last_change", &pwe); + if (retval == PWDB_SUCCESS) { + (void) pwdb_entry_delete(&pwe); + pwdb_set_entry(pw, "last_change", NULL, -1, NULL, NULL, 0); + } + + /* + * next check for pam.conf specified databases: shadow etc... [In + * other words, pam.conf indicates which database the password is + * to be subsequently placed in: this is password migration]. + */ + + if ( on(UNIX__SET_DB, ctrl) ) { + const char *db_token; + pwdb_type pt = _PWDB_MAX_TYPES; + + if ( on(UNIX_UNIX, ctrl) ) { + db_token = "U"; /* XXX - should be macro */ + pt = PWDB_UNIX; + } else if ( on(UNIX_SHADOW, ctrl) ) { + db_token = "x"; /* XXX - should be macro */ + pt = PWDB_SHADOW; + } else if ( on(UNIX_RADIUS, ctrl) ) { + db_token = "R"; /* XXX - is this ok? */ + pt = PWDB_RADIUS; + } else { + _log_err(LOG_ALERT + , "cannot determine database to use for authtok"); + pass_new = NULL; + if (pw) + (void) pwdb_delete(&pw); + return PAM_ABORT; /* we're in trouble */ + } + + /* + * Attempt to update the indicated database (only) + */ + + { + pwdb_type tpt[2]; + tpt[0] = pt; + tpt[1] = _PWDB_MAX_TYPES; + + /* Can we set entry in database? */ + retval = pwdb_flags("user", tpt, &flag); + if (retval == PWDB_SUCCESS && !pwdb_on(flag,PWDB_F_NOUPDATE)) { + /* YES. This database is available.. */ + + /* Only update if it is not already in the default list */ + for (i=0; pw->source[i] != _PWDB_MAX_TYPES + && pw->source[i] != pt ; ++i); + if (pw->source[i] == _PWDB_MAX_TYPES) { + const struct pwdb *tpw=NULL; + + /* copy database entry */ + if ((retval = pwdb_new(&tpw, 10)) != PWDB_SUCCESS + || (retval = pwdb_merge(tpw, pw, PWDB_TRUE)) + != PWDB_SUCCESS) { + _log_err(LOG_CRIT, "failed to obtain new pwdb: %s" + , pwdb_strerror(retval)); + retval = PAM_ABORT; + } else + retval = PAM_SUCCESS; + + /* set db_token */ + if (retval == PAM_SUCCESS) { + retval = pwdb_set_entry(tpw, "defer_pass", db_token + , 1+strlen(db_token) + , NULL, NULL, 0); + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "set defer_pass -> %s" + , pwdb_strerror(retval)); + retval = PAM_PERM_DENIED; + } else + retval = PAM_SUCCESS; + } + + /* update specific database */ + if (retval == PAM_SUCCESS) { + retval = pwdb_replace("user", tpt + , user, PWDB_ID_UNKNOWN, &tpw); + if (retval != PWDB_SUCCESS) { + const char *service=NULL; + (void) pam_get_item(pamh, PAM_SERVICE + , (const void **)&service); + _log_err(LOG_ALERT + , "(%s) specified database failed: %s" + , service + , pwdb_strerror(retval)); + retval = PAM_PERM_DENIED; + } else { + retval = PAM_SUCCESS; + } + } + + /* clean up temporary pwdb */ + if (tpw) + (void) pwdb_delete(&tpw); + } + + /* we can properly adopt new defer_pass */ + if (retval == PAM_SUCCESS) { + /* failing here will mean we go back to former + password location */ + (void) pwdb_set_entry(pw, "defer_pass", db_token + , 1+strlen(db_token), NULL, NULL, 0); + } + } + } + } + + /* + * the password will now be placed in appropriate (perhaps original) db + */ + + retval = pwdb_get_entry(pw, "uid", &pwe); + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "no uid!? (%s); %s", user, pwdb_strerror(retval)); + pass_new = NULL; + if (pw) + (void) pwdb_delete(&pw); + return PAM_USER_UNKNOWN; + } + + /* insert the passwd into the 'pw' structure */ + + retval = pwdb_set_entry(pw, "passwd", pass_new, 1+strlen(pass_new) + , NULL, NULL, 0); + pass_new = NULL; + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "set2 failed; %s", pwdb_strerror(retval)); + if (pw) + (void) pwdb_delete(&pw); + return PAM_AUTHTOK_LOCK_BUSY; + } + + retval = pwdb_replace("user", pw->source, user + , *((uid_t *)pwe->value), &pw); + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "user (%s/%d) update failed; %s" + , user, *((uid_t *)pwe->value), pwdb_strerror(retval)); + if (pw) + (void) pwdb_delete(&pw); + (void) pwdb_entry_delete(&pwe); + return PAM_ABORT; + } + + if (retval != PWDB_SUCCESS) { + + _log_err(LOG_ALERT, "user (%s/%d) update failed; %s" + , user, *((uid_t *)pwe->value), pwdb_strerror(retval)); + retval = PAM_ABORT; + + } else { + /* password updated */ + + _log_err(LOG_INFO, "password for (%s/%d) changed by (%s/%d)" + , user, *((uid_t *)pwe->value), getlogin(), getuid()); + retval = PAM_SUCCESS; + } + + /* tidy up */ + + (void) pwdb_entry_delete(&pwe); + if (pw) + (void) pwdb_delete(&pw); + + return retval; +} + +/* ****************************************************************** + * Copyright (c) Andrew Morgan 1996,1997. + * Copyright (c) Cristian Gafton, 1996, 1997. + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/pam_unix_sess.-c b/modules/pam_pwdb/pam_unix_sess.-c new file mode 100644 index 00000000..e1fe820b --- /dev/null +++ b/modules/pam_pwdb/pam_unix_sess.-c @@ -0,0 +1,118 @@ +/* + * $Id$ + * + * $Log$ + * Revision 1.1 2000/06/20 22:11:51 agmorgan + * Initial revision + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.4 1996/12/01 03:05:54 morgan + * debugging with _pam_macros.h + * + * Revision 1.3 1996/11/10 21:05:33 morgan + * pwdb conversion + * + * Revision 1.2 1996/09/05 06:49:02 morgan + * more informative logging + * + * Revision 1.1 1996/08/29 13:27:51 morgan + * Initial revision + * + * + * See end for Copyright information + */ + +static const char rcsid_sess[] = +"$Id$\n" +" - PAM_PWDB session management. morgan@parc.power.net"; + +/* Define internal functions */ + +static int _unix_open_session(pam_handle_t *pamh, unsigned int ctrl) +{ + int retval; + char *user_name, *service; + + D(("called.")); + + retval = pam_get_item( pamh, PAM_USER, (void *) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + _log_err(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; + } + + retval = pam_get_item( pamh, PAM_SERVICE, (void*) &service ); + if ( service == NULL || retval != PAM_SUCCESS ) { + _log_err(LOG_CRIT, "open_session - error recovering service"); + return PAM_SESSION_ERR; + } + + _log_err(LOG_INFO, "(%s) session opened for user %s by %s(uid=%d)" + , service, user_name + , getlogin() == NULL ? "":getlogin(), getuid() ); + + return PAM_SUCCESS; +} + +static int _unix_close_session(pam_handle_t *pamh, unsigned int ctrl) +{ + int retval; + char *user_name, *service; + + D(("called.")); + + retval = pam_get_item( pamh, PAM_USER, (void*) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + _log_err(LOG_CRIT, "close_session - error recovering username"); + return PAM_SESSION_ERR; + } + + retval = pam_get_item( pamh, PAM_SERVICE, (void*) &service ); + if ( service == NULL || retval != PAM_SUCCESS ) { + _log_err(LOG_CRIT, "close_session - error recovering service"); + return PAM_SESSION_ERR; + } + + _log_err(LOG_INFO, "(%s) session closed for user %s" + , service, user_name ); + + return PAM_SUCCESS; +} + +/* + * Copyright (c) Alexander O. Yuriev, 1996. All rights reserved. + * Copyright (c) Andrew G. Morgan, 1996, + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/pwdb_chkpwd.c b/modules/pam_pwdb/pwdb_chkpwd.c new file mode 100644 index 00000000..2c7ba29c --- /dev/null +++ b/modules/pam_pwdb/pwdb_chkpwd.c @@ -0,0 +1,204 @@ +/* + * $Id$ + * + * This program is designed to run setuid(root) or with sufficient + * privilege to read all of the unix password databases. It is designed + * to provide a mechanism for the current user (defined by this + * process' real uid) to verify their own password. + * + * The password is read from the standard input. The exit status of + * this program indicates whether the user is authenticated or not. + * + * Copyright information is located at the end of the file. + * + */ + +#define _BSD_SOURCE + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#define MAXPASS 200 /* the maximum length of a password */ + +#define UNIX_PASSED (PWDB_SUCCESS) +#define UNIX_FAILED (PWDB_SUCCESS+1) + +#include + +/* syslogging function for errors and other information */ + +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("pwdb_chkpwd", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +#define PWDB_NO_MD_COMPAT +#include "pam_unix_md.-c" + +static int _unix_verify_passwd(const char *salt, const char *p) +{ + char *pp=NULL; + int retval; + + if (p == NULL) { + if (*salt == '\0') { + retval = UNIX_PASSED; + } else { + retval = UNIX_FAILED; + } + } else { + pp = _pam_md(p, salt); + p = NULL; /* no longer needed here */ + + if ( strcmp( pp, salt ) == 0 ) { + retval = UNIX_PASSED; + } else { + retval = UNIX_FAILED; + } + } + + /* clean up */ + { + char *tp = pp; + if (pp != NULL) { + while(tp && *tp) + *tp++ = '\0'; + free(pp); + pp = tp = NULL; + } + } + + return retval; +} + +int main(void) +{ + const struct pwdb *pw=NULL; + const struct pwdb_entry *pwe=NULL; + char pass[MAXPASS+1]; + int npass; + int retval=UNIX_FAILED; + + /* + * we establish that this program is running with non-tty stdin. + * this is to discourage casual use. It does *NOT* prevent an + * intruder from repeatadly running this program to determine the + * password of the current user (brute force attack, but one for + * which the attacker must already have gained access to the user's + * account). + */ + + if ( isatty(STDIN_FILENO) ) { + _log_err(LOG_NOTICE + , "inappropriate use of PWDB helper binary [UID=%d]" + , getuid() ); + fprintf(stderr, + "This program is not designed for running in this way\n" + "-- the system administrator has been informed\n"); + exit(UNIX_FAILED); + } + + /* + * determine the current user's name: + */ + + retval = pwdb_start(); + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "failed to open pwdb"); + retval = UNIX_FAILED; + } + if (retval != UNIX_FAILED) { + retval = pwdb_locate("user", PWDB_DEFAULT, PWDB_NAME_UNKNOWN + , getuid(), &pw); + } + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "could not identify user"); + while (pwdb_end() != PWDB_SUCCESS); + exit(UNIX_FAILED); + } + + /* read the password from stdin (a pipe from the pam_pwdb module) */ + + npass = read(STDIN_FILENO, pass, MAXPASS); + + if (npass < 0) { /* is it a valid password? */ + _log_err(LOG_DEBUG, "no password supplied"); + retval = UNIX_FAILED; + } else if (npass >= MAXPASS-1) { + _log_err(LOG_DEBUG, "password too long"); + retval = UNIX_FAILED; + } else if (pwdb_get_entry(pw, "passwd", &pwe) != PWDB_SUCCESS) { + _log_err(LOG_WARNING, "password not found"); + retval = UNIX_FAILED; + } else { + if (npass <= 0) { + /* the password is NULL */ + + retval = _unix_verify_passwd((const char *)(pwe->value), NULL); + } else { + /* does pass agree with the official one? */ + + pass[npass] = '\0'; /* NUL terminate */ + retval = _unix_verify_passwd((const char *)(pwe->value), pass); + } + } + + memset(pass, '\0', MAXPASS); /* clear memory of the password */ + while (pwdb_end() != PWDB_SUCCESS); + + /* return pass or fail */ + + exit(retval); +} + +/* + * Copyright (c) Andrew G. Morgan, 1997. All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_pwdb/support.-c b/modules/pam_pwdb/support.-c new file mode 100644 index 00000000..2cbcb576 --- /dev/null +++ b/modules/pam_pwdb/support.-c @@ -0,0 +1,938 @@ +/* + * $Id$ + * + * Copyright information at end of file. + */ + +/* + * here is the string to inform the user that the new passwords they + * typed were not the same. + */ + +#define MISTYPED_PASS "Sorry, passwords do not match" + +/* type definition for the control options */ + +typedef struct { + const char *token; + unsigned int mask; /* shall assume 32 bits of flags */ + unsigned int flag; +} UNIX_Ctrls; + +/* + * macro to determine if a given flag is on + */ + +#define on(x,ctrl) (unix_args[x].flag & ctrl) + +/* + * macro to determine that a given flag is NOT on + */ + +#define off(x,ctrl) (!on(x,ctrl)) + +/* + * macro to turn on/off a ctrl flag manually + */ + +#define set(x,ctrl) (ctrl = ((ctrl)&unix_args[x].mask)|unix_args[x].flag) +#define unset(x,ctrl) (ctrl &= ~(unix_args[x].flag)) + +/* the generic mask */ + +#define _ALL_ON_ (~0U) + +/* end of macro definitions definitions for the control flags */ + +/* ****************************************************************** * + * ctrl flags proper.. + */ + +/* + * here are the various options recognized by the unix module. They + * are enumerated here and then defined below. Internal arguments are + * given NULL tokens. + */ + +#define UNIX__OLD_PASSWD 0 /* internal */ +#define UNIX__VERIFY_PASSWD 1 /* internal */ +#define UNIX__IAMROOT 2 /* internal */ + +#define UNIX_AUDIT 3 /* print more things than debug.. + some information may be sensitive */ +#define UNIX_USE_FIRST_PASS 4 +#define UNIX_TRY_FIRST_PASS 5 +#define UNIX_NOT_SET_PASS 6 /* don't set the AUTHTOK items */ + +#define UNIX__PRELIM 7 /* internal */ +#define UNIX__UPDATE 8 /* internal */ +#define UNIX__NONULL 9 /* internal */ +#define UNIX__QUIET 10 /* internal */ +#define UNIX_USE_AUTHTOK 11 /* insist on reading PAM_AUTHTOK */ +#define UNIX_SHADOW 12 /* signal shadow on */ +#define UNIX_MD5_PASS 13 /* force the use of MD5 passwords */ +#define UNIX__NULLOK 14 /* Null token ok */ +#define UNIX_RADIUS 15 /* wish to use RADIUS for password */ +#define UNIX__SET_DB 16 /* internal - signals redirect to db */ +#define UNIX_DEBUG 17 /* send more info to syslog(3) */ +#define UNIX_NODELAY 18 /* admin does not want a fail-delay */ +#define UNIX_UNIX 19 /* wish to use /etc/passwd for pwd */ +#define UNIX_BIGCRYPT 20 /* use DEC-C2 crypt()^x function */ +#define UNIX_LIKE_AUTH 21 /* need to auth for setcred to work */ +/* -------------- */ +#define UNIX_CTRLS_ 22 /* number of ctrl arguments defined */ + + +static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = { +/* symbol token name ctrl mask ctrl * + * ------------------ ------------------ -------------- ---------- */ + +/* UNIX__OLD_PASSWD */ { NULL, _ALL_ON_, 01 }, +/* UNIX__VERIFY_PASSWD */ { NULL, _ALL_ON_, 02 }, +/* UNIX__IAMROOT */ { NULL, _ALL_ON_, 04 }, +/* UNIX_AUDIT */ { "audit", _ALL_ON_, 010 }, +/* UNIX_USE_FIRST_PASS */ { "use_first_pass", _ALL_ON_^(060), 020 }, +/* UNIX_TRY_FIRST_PASS */ { "try_first_pass", _ALL_ON_^(060), 040 }, +/* UNIX_NOT_SET_PASS */ { "not_set_pass", _ALL_ON_, 0100 }, +/* UNIX__PRELIM */ { NULL, _ALL_ON_^(0600), 0200 }, +/* UNIX__UPDATE */ { NULL, _ALL_ON_^(0600), 0400 }, +/* UNIX__NONULL */ { NULL, _ALL_ON_, 01000 }, +/* UNIX__QUIET */ { NULL, _ALL_ON_, 02000 }, +/* UNIX_USE_AUTHTOK */ { "use_authtok", _ALL_ON_, 04000 }, +/* UNIX_SHADOW */ { "shadow", _ALL_ON_^(0140000), 010000 }, +/* UNIX_MD5_PASS */ { "md5", _ALL_ON_^(02000000), 020000 }, +/* UNIX__NULLOK */ { "nullok", _ALL_ON_^(01000), 0 }, +/* UNIX_RADIUS */ { "radius", _ALL_ON_^(0110000), 040000 }, +/* UNIX__SET_DB */ { NULL, _ALL_ON_, 0100000 }, +/* UNIX_DEBUG */ { "debug", _ALL_ON_, 0200000 }, +/* UNIX_NODELAY */ { "nodelay", _ALL_ON_, 0400000 }, +/* UNIX_UNIX */ { "unix", _ALL_ON_^(050000), 01000000 }, +/* UNIX_BIGCRYPT */ { "bigcrypt", _ALL_ON_^(020000), 02000000 }, +/* UNIX_LIKE_AUTH */ { "likeauth", _ALL_ON_, 04000000 }, +}; + +#define UNIX_DEFAULTS (unix_args[UNIX__NONULL].flag) + +/* syslogging function for errors and other information */ + +static void _log_err(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM_pwdb", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* this is a front-end for module-application conversations */ + +static int converse(pam_handle_t *pamh, int ctrl, int nargs + , struct pam_message **message + , struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; + if ( retval == PAM_SUCCESS ) { + + retval = conv->conv(nargs, ( const struct pam_message ** ) message + , response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && on(UNIX_DEBUG,ctrl) ) { + _log_err(LOG_DEBUG, "conversation failure [%s]" + , pam_strerror(pamh, retval)); + } + + } else if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + , pam_strerror(pamh, retval)); + } + + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +static int make_remark(pam_handle_t *pamh, unsigned int ctrl + , int type, const char *text) +{ + int retval=PAM_SUCCESS; + + if ( off(UNIX__QUIET, ctrl) ) { + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + + resp = NULL; + retval = converse(pamh, ctrl, 1, pmsg, &resp); + + if (resp) { + _pam_drop_reply(resp, 1); + } + } + return retval; +} + +/* + * set the control flags for the UNIX module. + */ + +static int set_ctrl(int flags, int argc, const char **argv) +{ + unsigned int ctrl; + + D(("called.")); + + ctrl = UNIX_DEFAULTS; /* the default selection of options */ + + /* set some flags manually */ + + if ( getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK) ) { + set(UNIX__IAMROOT, ctrl); + } + if ( flags & PAM_UPDATE_AUTHTOK ) { + set(UNIX__UPDATE, ctrl); + } + if ( flags & PAM_PRELIM_CHECK ) { + set(UNIX__PRELIM, ctrl); + } + if ( flags & PAM_DISALLOW_NULL_AUTHTOK ) { + set(UNIX__NONULL, ctrl); + } + if ( flags & PAM_SILENT ) { + set(UNIX__QUIET, ctrl); + } + + /* now parse the arguments to this module */ + + while (argc-- > 0) { + int j; + + D(("pam_pwdb arg: %s",*argv)); + + for (j=0; j= UNIX_CTRLS_ ) { + _log_err(LOG_ERR, "unrecognized option [%s]",*argv); + } else { + ctrl &= unix_args[j].mask; /* for turning things off */ + ctrl |= unix_args[j].flag; /* for turning things on */ + } + + ++argv; /* step to next argument */ + } + + /* these are used for updating passwords in specific places */ + + if (on(UNIX_SHADOW,ctrl) || on(UNIX_RADIUS,ctrl) || on(UNIX_UNIX,ctrl)) { + set(UNIX__SET_DB, ctrl); + } + + /* auditing is a more sensitive version of debug */ + + if ( on(UNIX_AUDIT,ctrl) ) { + set(UNIX_DEBUG, ctrl); + } + + /* return the set of flags */ + + D(("done.")); + return ctrl; +} + +/* use this to free strings. ESPECIALLY password strings */ + +static char *_pam_delete(register char *xx) +{ + _pam_overwrite(xx); + _pam_drop(xx); + return NULL; +} + +static void _cleanup(pam_handle_t *pamh, void *x, int error_status) +{ + x = _pam_delete( (char *) x ); +} + +/* ************************************************************** * + * Useful non-trivial functions * + * ************************************************************** */ + +#include "pam_unix_md.-c" + +/* + * the following is used to keep track of the number of times a user fails + * to authenticate themself. + */ + +#define FAIL_PREFIX "-UN*X-FAIL-" +#define UNIX_MAX_RETRIES 3 + +struct _pam_failed_auth { + char *user; /* user that's failed to be authenticated */ + char *name; /* attempt from user with name */ + int id; /* uid of name'd user */ + int count; /* number of failures so far */ +}; + +#ifndef PAM_DATA_REPLACE +#error "Need to get an updated libpam 0.52 or better" +#endif + +static void _cleanup_failures(pam_handle_t *pamh, void *fl, int err) +{ + int quiet; + const char *service=NULL; + struct _pam_failed_auth *failure; + + D(("called")); + + quiet = err & PAM_DATA_SILENT; /* should we log something? */ + err &= PAM_DATA_REPLACE; /* are we just replacing data? */ + failure = (struct _pam_failed_auth *) fl; + + if ( failure != NULL ) { + + if ( !quiet && !err ) { /* under advisement from Sun,may go away */ + + /* log the number of authentication failures */ + if ( failure->count > 1 ) { + (void) pam_get_item(pamh, PAM_SERVICE + , (const void **)&service); + _log_err(LOG_NOTICE + , "%d more authentication failure%s; %s(uid=%d) -> " + "%s for %s service" + , failure->count-1, failure->count==2 ? "":"s" + , failure->name + , failure->id + , failure->user + , service == NULL ? "**unknown**":service + ); + if ( failure->count > UNIX_MAX_RETRIES ) { + _log_err(LOG_ALERT + , "service(%s) ignoring max retries; %d > %d" + , service == NULL ? "**unknown**":service + , failure->count + , UNIX_MAX_RETRIES ); + } + } + } + failure->user = _pam_delete(failure->user); /* tidy up */ + failure->name = _pam_delete(failure->name); /* tidy up */ + free(failure); + } +} + +/* + * verify the password of a user + */ + +#include +#include + +static int pwdb_run_helper_binary(pam_handle_t *pamh, const char *passwd) +{ + int retval, child, fds[2]; + + D(("called.")); + /* create a pipe for the password */ + if (pipe(fds) != 0) { + D(("could not make pipe")); + return PAM_AUTH_ERR; + } + + /* fork */ + child = fork(); + if (child == 0) { + static char *args[] = { NULL, NULL }; + static char *envp[] = { NULL }; + + /* XXX - should really tidy up PAM here too */ + while (pwdb_end() == PWDB_SUCCESS); + + /* reopen stdin as pipe */ + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + + /* exec binary helper */ + args[0] = x_strdup(CHKPWD_HELPER); + execve(CHKPWD_HELPER, args, envp); + + /* should not get here: exit with error */ + D(("helper binary is not available")); + exit(PWDB_SUCCESS+1); + } else if (child > 0) { + /* wait for child */ + close(fds[0]); + if (passwd != NULL) { /* send the password to the child */ + write(fds[1], passwd, strlen(passwd)+1); + passwd = NULL; + } else { + write(fds[1], "", 1); /* blank password */ + } + close(fds[1]); + (void) waitpid(child, &retval, 0); /* wait for helper to complete */ + retval = (retval == PWDB_SUCCESS) ? PAM_SUCCESS:PAM_AUTH_ERR; + } else { + D(("fork failed")); + retval = PAM_AUTH_ERR; + } + + D(("returning %d", retval)); + return retval; +} + +static int _unix_verify_password(pam_handle_t *pamh, const char *name + , const char *p, unsigned int ctrl) +{ + const struct pwdb *pw=NULL; + const struct pwdb_entry *pwe=NULL; + + const char *salt; + char *pp; + char *data_name; + int retval; + int verify_result; + + D(("called")); + +#ifdef HAVE_PAM_FAIL_DELAY + if ( off(UNIX_NODELAY, ctrl) ) { + D(("setting delay")); + (void) pam_fail_delay(pamh, 1000000); /* 1 sec delay for on failure */ + } +#endif + + /* locate the entry for this user */ + + D(("locating user's record")); + retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw); + if (retval == PWDB_PASS_PHRASE_REQD) { + /* + * give the password to the pwdb library. It may be needed to + * access the database + */ + + retval = pwdb_set_entry( pw, "pass_phrase", p, 1+strlen(p) + , NULL, NULL, 0); + if (retval != PWDB_SUCCESS) { + _log_err(LOG_ALERT, "find pass; %s", pwdb_strerror(retval)); + (void) pwdb_delete(&pw); + p = NULL; + return PAM_CRED_INSUFFICIENT; + } + + retval = pwdb_locate("user", pw->source, name, PWDB_ID_UNKNOWN, &pw); + } + + if (retval != PWDB_SUCCESS) { + D(("user's record unavailable")); + if ( on(UNIX_AUDIT, ctrl) ) { + /* this might be a typo and the user has given a password + instead of a username. Careful with this. */ + _log_err(LOG_ALERT, "check pass; user (%s) unknown", name); + } else { + _log_err(LOG_ALERT, "check pass; user unknown"); + } + (void) pwdb_delete(&pw); + p = NULL; + return PAM_USER_UNKNOWN; + } + + /* + * courtesy of PWDB the password for the user is stored in + * encrypted form in the "passwd" entry of pw. + */ + + retval = pwdb_get_entry(pw, "passwd", &pwe); + if (retval != PWDB_SUCCESS) { + if (geteuid()) { + /* we are not root perhaps this is the reason? Run helper */ + D(("running helper binary")); + retval = pwdb_run_helper_binary(pamh, p); + } else { + retval = PAM_AUTHINFO_UNAVAIL; + _log_err(LOG_ALERT, "get passwd; %s", pwdb_strerror(retval)); + } + (void) pwdb_delete(&pw); + p = NULL; + return retval; + } + salt = (const char *) pwe->value; + + /* + * XXX: Cristian, the above is not the case for RADIUS(?) Some + * lines should be added for RADIUS to verify the password in + * clear text... + */ + + data_name = (char *) malloc(sizeof(FAIL_PREFIX)+strlen(name)); + if ( data_name == NULL ) { + _log_err(LOG_CRIT, "no memory for data-name"); + } + strcpy(data_name, FAIL_PREFIX); + strcpy(data_name + sizeof(FAIL_PREFIX)-1, name); + + if ( !( (salt && *salt) || (p && *p) ) ) { + + D(("two null passwords to compare")); + + /* the stored password is NULL */ + pp = NULL; + if ( off(UNIX__NONULL, ctrl ) ) { /* this means we've succeeded */ + verify_result = PAM_SUCCESS; + } else { + verify_result = PAM_AUTH_ERR; + } + + } else if ( !( salt && p ) ) { + + D(("one of the two to compare are NULL")); + + pp = NULL; + verify_result = PAM_AUTH_ERR; + + } else { + + pp = _pam_md(p, salt); + + /* the moment of truth -- do we agree with the password? */ + D(("comparing state of pp[%s] and salt[%s]", pp, salt)); + + if ( strcmp( pp, salt ) == 0 ) { + verify_result = PAM_SUCCESS; + } else { + _pam_delete(pp); + pp = _pam_md_compat(p, salt); + if ( strcmp( pp, salt ) == 0 ) { + verify_result = PAM_SUCCESS; + } else { + verify_result = PAM_AUTH_ERR; + } + } + + p = NULL; /* no longer needed here */ + + } + + if ( verify_result == PAM_SUCCESS ) { + + retval = PAM_SUCCESS; + if (data_name) { /* reset failures */ + pam_set_data(pamh, data_name, NULL, _cleanup_failures); + } + + } else { + + retval = PAM_AUTH_ERR; + if (data_name != NULL) { + struct _pam_failed_auth *new=NULL; + const struct _pam_failed_auth *old=NULL; + + /* get a failure recorder */ + + new = (struct _pam_failed_auth *) + malloc(sizeof(struct _pam_failed_auth)); + + if (new != NULL) { + + new->user = x_strdup(name); + new->id = getuid(); + new->name = x_strdup(getlogin() ? getlogin():"" ); + + /* any previous failures for this user ? */ + pam_get_data(pamh, data_name, (const void **)&old ); + + if (old != NULL) { + new->count = old->count +1; + if (new->count >= UNIX_MAX_RETRIES) { + retval = PAM_MAXTRIES; + } + } else { + const char *service=NULL; + (void) pam_get_item(pamh, PAM_SERVICE + , (const void **)&service); + _log_err(LOG_NOTICE + , "authentication failure; %s(uid=%d) -> " + "%s for %s service" + , new->name + , new->id + , new->user + , service == NULL ? "**unknown**":service + ); + new->count = 1; + } + + pam_set_data(pamh, data_name, new, _cleanup_failures); + + } else { + _log_err(LOG_CRIT, "no memory for failure recorder"); + } + } + + } + + (void) pwdb_entry_delete(&pwe); + (void) pwdb_delete(&pw); + salt = NULL; + _pam_delete(data_name); + _pam_delete(pp); + + D(("done [%d].", retval)); + + return retval; +} + +/* + * this function obtains the name of the current user and ensures + * that the PAM_USER item is set to this value + */ + +static int _unix_get_user(pam_handle_t *pamh, unsigned int ctrl + , const char *prompt, const char **user) +{ + int retval; + + D(("called")); + + retval = pam_get_user(pamh, user, prompt); + if (retval != PAM_SUCCESS) { + D(("trouble reading username")); + return retval; + } + + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't take + * any chances here. Require that the username starts with an + * alphanumeric character. + */ + + if (*user == NULL || !isalnum(**user)) { + D(("bad username")); + if (on(UNIX_DEBUG,ctrl)) { + _log_err(LOG_ERR, "bad username [%s]", *user); + } + return PAM_USER_UNKNOWN; + } + + if (retval == PAM_SUCCESS && on(UNIX_DEBUG,ctrl)) { + _log_err(LOG_DEBUG, "username [%s] obtained", *user); + } + + return retval; +} + +/* + * _unix_blankpasswd() is a quick check for a blank password + * + * returns TRUE if user does not have a password + * - to avoid prompting for one in such cases (CG) + */ + +static int _unix_blankpasswd(unsigned int ctrl, const char *name) +{ + const struct pwdb *pw=NULL; + const struct pwdb_entry *pwe=NULL; + int retval; + + D(("called")); + + /* + * This function does not have to be too smart if something goes + * wrong, return FALSE and let this case to be treated somewhere + * else (CG) + */ + + if ( on(UNIX__NONULL, ctrl) ) + return 0; /* will fail but don't let on yet */ + + /* find the user's database entry */ + + retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw); + if (retval != PWDB_SUCCESS || pw == NULL ) { + + retval = 0; + + } else { + + /* Does this user have a password? */ + + retval = pwdb_get_entry(pw, "passwd", &pwe); + if ( retval != PWDB_SUCCESS || pwe == NULL ) + retval = 0; + else if ( pwe->value == NULL || ((char *)pwe->value)[0] == '\0' ) + retval = 1; + else + retval = 0; + + } + + /* tidy up */ + + if ( pw ) { + (void) pwdb_delete(&pw); + if ( pwe ) + (void) pwdb_entry_delete(&pwe); + } + + return retval; +} + +/* + * obtain a password from the user + */ + +static int _unix_read_password( pam_handle_t *pamh + , unsigned int ctrl + , const char *comment + , const char *prompt1 + , const char *prompt2 + , const char *data_name + , const char **pass ) +{ + int authtok_flag; + int retval; + const char *item; + char *token; + + D(("called")); + + /* + * make sure nothing inappropriate gets returned + */ + + *pass = token = NULL; + + /* + * which authentication token are we getting? + */ + + authtok_flag = on(UNIX__OLD_PASSWD,ctrl) ? PAM_OLDAUTHTOK:PAM_AUTHTOK ; + + /* + * should we obtain the password from a PAM item ? + */ + + if ( on(UNIX_TRY_FIRST_PASS,ctrl) || on(UNIX_USE_FIRST_PASS,ctrl) ) { + retval = pam_get_item(pamh, authtok_flag, (const void **) &item); + if (retval != PAM_SUCCESS ) { + /* very strange. */ + _log_err(LOG_ALERT + , "pam_get_item returned error to unix-read-password" + ); + return retval; + } else if (item != NULL) { /* we have a password! */ + *pass = item; + item = NULL; + return PAM_SUCCESS; + } else if (on(UNIX_USE_FIRST_PASS,ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } else if (on(UNIX_USE_AUTHTOK, ctrl) + && off(UNIX__OLD_PASSWD, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + + /* + * getting here implies we will have to get the password from the + * user directly. + */ + + { + struct pam_message msg[3],*pmsg[3]; + struct pam_response *resp; + int i, replies; + + /* prepare to converse */ + + if ( comment != NULL && off(UNIX__QUIET, ctrl) ) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = comment; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt1; + replies = 1; + + if ( prompt2 != NULL ) { + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt2; + ++replies; + } + + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, ctrl, i, pmsg, &resp); + + if (resp != NULL) { + + /* interpret the response */ + + if (retval == PAM_SUCCESS) { /* a good conversation */ + + token = x_strdup(resp[i-replies].resp); + if (token != NULL) { + if (replies == 2) { + + /* verify that password entered correctly */ + if (!resp[i-1].resp + || strcmp(token,resp[i-1].resp)) { + token = _pam_delete(token); /* mistyped */ + retval = PAM_AUTHTOK_RECOVER_ERR; + make_remark(pamh, ctrl + , PAM_ERROR_MSG, MISTYPED_PASS); + } + } + + } else { + _log_err(LOG_NOTICE + , "could not recover authentication token"); + } + + } + + /* + * tidy up the conversation (resp_retcode) is ignored + * -- what is it for anyway? AGM + */ + + _pam_drop_reply(resp, i); + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR:retval ; + } + } + + if (retval != PAM_SUCCESS) { + if ( on(UNIX_DEBUG,ctrl) ) + _log_err(LOG_DEBUG,"unable to obtain a password"); + return retval; + } + + /* 'token' is the entered password */ + + if ( off(UNIX_NOT_SET_PASS, ctrl) ) { + + /* we store this password as an item */ + + retval = pam_set_item(pamh, authtok_flag, token); + token = _pam_delete(token); /* clean it up */ + if ( retval != PAM_SUCCESS + || (retval = pam_get_item(pamh, authtok_flag + , (const void **)&item)) + != PAM_SUCCESS ) { + + _log_err(LOG_CRIT, "error manipulating password"); + return retval; + + } + + } else { + /* + * then store it as data specific to this module. pam_end() + * will arrange to clean it up. + */ + + retval = pam_set_data(pamh, data_name, (void *) token, _cleanup); + if (retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "error manipulating password data [%s]" + , pam_strerror(pamh, retval) ); + token = _pam_delete(token); + return retval; + } + item = token; + token = NULL; /* break link to password */ + } + + *pass = item; + item = NULL; /* break link to password */ + + return PAM_SUCCESS; +} + +static int _pam_unix_approve_pass(pam_handle_t *pamh + , unsigned int ctrl + , const char *pass_old + , const char *pass_new) +{ + D(("&new=%p, &old=%p",pass_old,pass_new)); + D(("new=[%s]",pass_new)); + D(("old=[%s]",pass_old)); + + if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) { + if ( on(UNIX_DEBUG, ctrl) ) { + _log_err(LOG_DEBUG, "bad authentication token"); + } + make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ? + "No password supplied":"Password unchanged" ); + return PAM_AUTHTOK_ERR; + } + + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place - AGM + */ + + return PAM_SUCCESS; +} + +/* ****************************************************************** * + * Copyright (c) Andrew G. Morgan 1996-8. + * Copyright (c) Alex O. Yuriev, 1996. + * Copyright (c) Cristian Gafton 1996. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/modules/pam_radius/.cvsignore b/modules/pam_radius/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_radius/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_radius/Makefile b/modules/pam_radius/Makefile new file mode 100644 index 00000000..a74b911f --- /dev/null +++ b/modules/pam_radius/Makefile @@ -0,0 +1,99 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Cristian Gafton 1996/09/10 +# +# STATIC modules are not supported +# + +TITLE=pam_radius +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/radius.conf +export CONFILE + +ifeq ($(HAVE_PWDBLIB),yes) + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o + +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +#LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +#static/%.o : %.c +# $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +#ifdef STATIC +#LIBSTATIC = lib$(TITLE).o +#endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +#ifdef STATIC +# $(MKDIR) ./static +#endif + +register: +#ifdef STATIC +# ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +#endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) -lpwdb +endif + +#ifdef STATIC +#$(LIBOBJS): $(LIBSRC) +# +#$(LIBSTATIC): $(LIBOBJS) +# $(LD) -r -o $@ $(LIBOBJS) -lpwdb +#endif + +install: all +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + +else + +include ../dont_makefile + +endif diff --git a/modules/pam_radius/README b/modules/pam_radius/README new file mode 100644 index 00000000..253308fd --- /dev/null +++ b/modules/pam_radius/README @@ -0,0 +1,58 @@ + +pam_radius module: + RADIUS session module. + +WHAT IT DOES: + This module is intended to provide the session service for users +autheticated with a RADIUS server. At the present stage, the only option +supported is the use of the RADIUS server as an accounting server. There are +few things which needs to be cleared out first in the PAM project until one +will be able to use this module and expect it to magically start pppd in +response to a RADIUS server command to use PPP for this user, or to initiate +a telnet connection to another host, or to hang and call back the user using +parameters provided in the RADIUS server response. Most of these things are +better suited for the radius login application. I hope to make available +Real Soon (tm) patches for the login apps to make it work this way. + + +ARGUMENTS RECOGNIZED: + debug verbose logging + +MODULE SERVICES PROVIDED: + session _open_session and _close_session + + When opening a session, this module sends an Accounting-Start +message to the RADIUS server, which will log/update/whatever a database for +this user. On close, an Accounting-Stop message is sent to the RADIUS +server. + +This module have no other pre-requisites for making it work. One can install +a RADIUS server just for fun and use it as a centralized accounting server and +forget about wtmp/last/sac&comp :-) + +USAGE: + For the services you need this module (login for example) put + the following line in /etc/pam.conf as the last line for that + service (usually after the pam_unix session line): + + login session required /lib/security/pam_radius.so + + Replace "login" for each service you are using this module. + + This module make extensive use of the API provided in libpwdb + 0.54preB or later. By default, it will read the radius server + configuration (hostname and secret) from /etc/raddb/server. This is + a default compiled into libpwdb, and curently there is no way to + modify this default without recompiling libpwdb. I am working on + extending the radius support from libpwdb to provide a possibility + to make this runtime-configurable. + + Also please note that libpwdb will require also the RADIUS + dictionary to be present (/etc/raddb/dictionary). + +TODO: + The work is far from complete. Deal with "real" session things. + +AUTHOR: + Cristian Gafton + diff --git a/modules/pam_radius/pam_radius.c b/modules/pam_radius/pam_radius.c new file mode 100644 index 00000000..b412edf9 --- /dev/null +++ b/modules/pam_radius/pam_radius.c @@ -0,0 +1,193 @@ +/* + * pam_radius + * Process an user session according to a RADIUS server response + * + * 1.0 - initial release - Linux ONLY + * 1.1 - revised and reorganized for libpwdb 0.54preB or higher + * - removed the conf= parameter, since we use libpwdb exclusively now + * + * See end for Copyright information + */ + +#if !(defined(linux)) +#error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!! +#endif + +/* Module defines */ +#define BUFFER_SIZE 1024 +#define LONG_VAL_PTR(ptr) ((*(ptr)<<24)+(*((ptr)+1)<<16)+(*((ptr)+2)<<8)+(*((ptr)+3))) + +#define PAM_SM_SESSION + +#include "pam_radius.h" + +#include +#include + +static time_t session_time; + +/* we need to save these from open_session to close_session, since + * when close_session will be called we won't be root anymore and + * won't be able to access again the radius server configuration file + * -- cristiang */ + +static RADIUS_SERVER rad_server; +static char hostname[BUFFER_SIZE]; +static char secret[BUFFER_SIZE]; + +/* logging */ +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("pam_radius", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 0x0001 + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* now the session stuff */ +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int retval; + char *user_name; + int ctrl; + + ctrl = _pam_parse(argc, argv); + retval = pam_get_item( pamh, PAM_USER, (void*) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + _pam_log(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; + } + + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "starting RADIUS user session for '%s'", + user_name); + + retval = get_server_entries(hostname, secret); + if ((retval != PWDB_RADIUS_SUCCESS) || + !strlen(hostname) || !strlen(secret)) { + _pam_log(LOG_CRIT, "Could not determine the radius server to talk to"); + return PAM_IGNORE; + } + session_time = time(NULL); + rad_server.hostname = hostname; + rad_server.secret = secret; + retval = radius_acct_start(rad_server, user_name); + if (retval != PWDB_RADIUS_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "ERROR communicating with the RADIUS server"); + return PAM_IGNORE; + } + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int ctrl; + char *user_name; + int retval; + + ctrl = _pam_parse(argc, argv); + retval = pam_get_item( pamh, PAM_USER, (void*) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + _pam_log(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; + } + + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "closing RADIUS user session for '%s'", + user_name); + + if (!strlen(hostname) || !strlen(secret)) { + _pam_log(LOG_CRIT, "Could not determine the radius server to talk to"); + return PAM_IGNORE; + } + retval = radius_acct_stop(rad_server, user_name, + time(NULL) - session_time); + if (retval != PWDB_RADIUS_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG, "ERROR communicating with the RADIUS server"); + return PAM_IGNORE; + } + + return PAM_SUCCESS; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_radius_modstruct = { + "pam_radius", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL +}; +#endif + +/* + * Copyright (c) Cristian Gafton, 1996, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_radius/pam_radius.h b/modules/pam_radius/pam_radius.h new file mode 100644 index 00000000..1f860eb5 --- /dev/null +++ b/modules/pam_radius/pam_radius.h @@ -0,0 +1,38 @@ + +#ifndef PAM_RADIUS_H +#define PAM_RADIUS_H + +#define _GNU_SOURCE +#include + +#include + +#ifndef __USE_POSIX2 +#define __USE_POSIX2 +#endif /* __USE_POSIX2 */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +/******************************************************************/ + +#endif /* PAM_RADIUS_H */ diff --git a/modules/pam_rhosts/.cvsignore b/modules/pam_rhosts/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_rhosts/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_rhosts/Makefile b/modules/pam_rhosts/Makefile new file mode 100644 index 00000000..431842b2 --- /dev/null +++ b/modules/pam_rhosts/Makefile @@ -0,0 +1,98 @@ +# This Makefile controls a build process of the pam_rhosts modules +# for Linux-PAM. You should not modify this Makefile. + +LIBAUTHOBJ = pam_rhosts_auth.o +LIBAUTHSRC = pam_rhosts_auth.c +LIBSESSOBJ = +LIBSESSSRC = +LIBPASSWDSRC = +LIBPASSWDOBJ = +LIBOBJ = $(LIBAUTHOBJ) $(LIBSESSOBJ) $(LIBPASSWDOBJ) +LIBSRC = $(LIBAUTHSRC) $(LIBSESSSRC) $(LIBPASSWDSRC) + +ifdef STATIC +LIBSTATIC = libpam_rhosts.o +LIBOBJS = $(addprefix static/,$(LIBOBJ)) +endif + +ifdef DYNAMIC +LIBSESSSH = +LIBAUTHSH = pam_rhosts_auth.so +LIBPASSWDSH = +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBSHARED = $(LIBSESSSH) $(LIBAUTHSH) $(LIBPASSWDSH) +endif + +ifeq ($(shell if [ -f /usr/include/fsuid.h ]; then echo yes ; fi),yes) + CFLAGS += -DNEED_FSUID_H +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; \ + ./register_static pam_rhosts_auth pam_rhosts/libpam_rhosts.o ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +endif + +ifdef DYNAMIC +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +endif + +ifdef STATIC +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) + +endif + +#.c.o: +# $(CC) -c $(CFLAGS) $< + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +# tidy up + +remove: + cd $(FAKEROOT)$(SECUREDIR) && rm -f $(LIBSHARED) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) a.out core *~ + +extraclean: + rm -f *.a *.out *.o *.so *.bak dynamic/* static/* + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ diff --git a/modules/pam_rhosts/README b/modules/pam_rhosts/README new file mode 100644 index 00000000..527dfd38 --- /dev/null +++ b/modules/pam_rhosts/README @@ -0,0 +1,57 @@ +arguments recognized: + +"no_hosts_equiv" +"no_rhosts" +"debug" +"nowarn" +"suppress" +"promiscuous" + +.rhosts/hosts.equiv format: + +There are positive entries, when one is matched authentication +succeeds and terminates. There are negative entries, when one is +matched authentication fails and terminates. Thus order is +significant. + +Entry hosts.equiv .rhosts + All users on are ok Same username from is ok + from is ok ditto +- No users from are ok ditto + - from is not ok ditto + + can be ip (IPv4) numbers. + +Netgroups may be used in either host or user fields, and then applies +to all hosts, or users, in the netgroup. The syntax is + + +@ + +The entries + + +@ + +@ +@ + +@ + +means exactly what you think it does. Negative entries are of the +form + + -@ + +When the "promiscuous" option is given the special character + may be +used as a wildcard in any field. + + + Allow anyone from any host to connect. DANGEROUS. + + + Ditto. + + Allow the user to connect from anywhere. DANGEROUS. + + Allow any user from the host. Dangerous. + +These, perhaps more usefull, forms of the + form is also disallowed +unless "promiscuous" is specified: + + + - Disallow the user from any host + + -@ Disallow all members of the netgroup from any host + +When "promiscuous" is not specified a '+' is handled as a negative +match. + diff --git a/modules/pam_rhosts/pam_rhosts_auth.c b/modules/pam_rhosts/pam_rhosts_auth.c new file mode 100644 index 00000000..f9707cfc --- /dev/null +++ b/modules/pam_rhosts/pam_rhosts_auth.c @@ -0,0 +1,829 @@ +/*---------------------------------------------------------------------- + * Modified for Linux-PAM by Al Longyear 96/5/5 + * Modifications, Cristian Gafton 97/2/8 + * Modifications, Peter Allgeyer 97/3 + * Modifications (netgroups and fixes), Nicolai Langfeldt 97/3/21 + * Security fix: 97/10/2 - gethostbyname called repeatedly without care + * Modification (added privategroup option) Andrew + *---------------------------------------------------------------------- + * Copyright (c) 1983, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _BSD_SOURCE + +#define USER_RHOSTS_FILE "/.rhosts" /* prefixed by user's home dir */ + +#ifdef linux +#include +#endif + +#ifdef NEED_FSUID_H +#include +#endif /* NEED_FSUID_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* This is supposed(?) to contain the following */ +int innetgr(const char *, const char *, const char *,const char *); + +#include +#include +#include +#include + +#ifndef MAXDNAME +#define MAXDNAME 256 +#endif + +#include +#include + +#include +#ifdef linux +# include +# ifndef __USE_MISC +# define __USE_MISC +# include +# endif /* __USE_MISC */ +#endif + +#include +#include +#include +#include +#include +#include +#ifndef _PATH_HEQUIV +#define _PATH_HEQUIV "/etc/hosts.equiv" +#endif /* _PATH_HEQUIV */ + +#define PAM_SM_AUTH /* only defines this management group */ + +#include +#include + +/* to the best of my knowledge, all modern UNIX boxes have 32 bit integers */ +#define U32 unsigned int + + +/* + * Options for this module + */ + +struct _options { + int opt_no_hosts_equiv; + int opt_hosts_equiv_rootok; + int opt_no_rhosts; + int opt_debug; + int opt_nowarn; + int opt_disallow_null_authtok; + int opt_silent; + int opt_promiscuous; + int opt_suppress; + int opt_private_group; + int opt_no_uid_check; + const char *superuser; + const char *last_error; +}; + +/* logging */ +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("pam_rhosts_auth", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static void set_option (struct _options *opts, const char *arg) +{ + if (strcmp(arg, "no_hosts_equiv") == 0) { + opts->opt_no_hosts_equiv = 1; + return; + } + + if (strcmp(arg, "hosts_equiv_rootok") == 0) { + opts->opt_hosts_equiv_rootok = 1; + return; + } + + if (strcmp(arg, "no_rhosts") == 0) { + opts->opt_no_rhosts = 1; + return; + } + + if (strcmp(arg, "debug") == 0) { + D(("debugging enabled")); + opts->opt_debug = 1; + return; + } + + if (strcmp(arg, "no_warn") == 0) { + opts->opt_nowarn = 1; + return; + } + + if (strcmp(arg, "promiscuous") == 0) { + opts->opt_promiscuous = 1; /* used to permit '+' in ...hosts file */ + return; + } + + if (strcmp(arg, "suppress") == 0) { + opts->opt_suppress = 1; /* used to suppress failure warning message */ + return; + } + + if (strcmp(arg, "privategroup") == 0) { + opts->opt_private_group = 1; /* used to permit group write on .rhosts + file if group has same name as owner */ + return; + } + + if (strcmp(arg, "no_uid_check") == 0) { + opts->opt_no_uid_check = 1; /* NIS optimization */ + return; + } + + if (strcmp(arg, "superuser=") == 0) { + opts->superuser = arg+sizeof("superuser=")-1; + return; + } + /* + * All other options are ignored at the present time. + */ + _pam_log(LOG_WARNING, "unrecognized option '%s'", arg); +} + +static void set_parameters (struct _options *opts, int flags, + int argc, const char **argv) +{ + opts->opt_silent = flags & PAM_SILENT; + opts->opt_disallow_null_authtok = flags & PAM_DISALLOW_NULL_AUTHTOK; + + while (argc-- > 0) { + set_option (opts, *argv); + ++argv; + } +} + +/* + * Obtain the name of the remote host. Currently, this is simply by + * requesting the contents of the PAM_RHOST item. + */ + +static int pam_get_rhost(pam_handle_t *pamh, const char **rhost + , const char *prompt) +{ + int retval; + const char *current; + + retval = pam_get_item (pamh, PAM_RHOST, (const void **)¤t); + if (retval != PAM_SUCCESS) + return retval; + + if (current == NULL) { + return PAM_AUTH_ERR; + } + *rhost = current; + + return retval; /* pass on any error from conversation */ +} + +/* + * Obtain the name of the remote user. Currently, this is simply by + * requesting the contents of the PAM_RUSER item. + */ + +static int pam_get_ruser(pam_handle_t *pamh, const char **ruser + , const char *prompt) +{ + int retval; + const char *current; + + retval = pam_get_item (pamh, PAM_RUSER, (const void **)¤t); + if (retval != PAM_SUCCESS) + return retval; + + if (current == NULL) { + return PAM_AUTH_ERR; + } + *ruser = current; + + return retval; /* pass on any error from conversation */ +} + +/* + * Returns 1 if positive match, 0 if no match, -1 if negative match. + */ + +static int +__icheckhost (pam_handle_t *pamh, struct _options *opts, U32 raddr + , register char *lhost, const char *rhost) +{ + struct hostent *hp; + U32 laddr; + int negate=1; /* Multiply return with this to get -1 instead of 1 */ + char **pp, *user; + + /* Check nis netgroup. We assume that pam has done all needed + paranoia checking before we are handed the rhost */ + if (strncmp("+@",lhost,2) == 0) + return(innetgr(&lhost[2],rhost,NULL,NULL)); + + if (strncmp("-@",lhost,2) == 0) + return(-innetgr(&lhost[2],rhost,NULL,NULL)); + + /* -host */ + if (strncmp("-",lhost,1) == 0) { + negate=-1; + lhost++; + } else if (strcmp("+",lhost) == 0) { + (void) pam_get_item(pamh, PAM_USER, (const void **)&user); + D(("user %s has a `+' host entry", user)); + if (opts->opt_promiscuous) + return (1); /* asking for trouble, but ok.. */ + /* If not promiscuous: handle as negative */ + return (-1); + } + + /* Try for raw ip address first. */ + if (isdigit(*lhost) && (long)(laddr = inet_addr(lhost)) != -1) + return (negate*(! (raddr ^ laddr))); + + /* Better be a hostname. */ + hp = gethostbyname(lhost); + if (hp == NULL) + return (0); + + /* Spin through ip addresses. */ + for (pp = hp->h_addr_list; *pp; ++pp) + if (!memcmp (&raddr, *pp, sizeof (U32))) + return (negate); + + /* No match. */ + return (0); +} + +/* Returns 1 on positive match, 0 on no match, -1 on negative match */ + +static int __icheckuser(pam_handle_t *pamh, struct _options *opts + , const char *luser, const char *ruser + , const char *rhost) +{ + /* + luser is user entry from .rhosts/hosts.equiv file + ruser is user id on remote host + rhost is the remote host name + */ + char *user; + + /* [-+]@netgroup */ + if (strncmp("+@",luser,2) == 0) + return (innetgr(&luser[2],NULL,ruser,NULL)); + + if (strncmp("-@",luser,2) == 0) + return (-innetgr(&luser[2],NULL,ruser,NULL)); + + /* -user */ + if (strncmp("-",luser,1) == 0) + return(-(strcmp(&luser[1],ruser) == 0)); + + /* + */ + if (strcmp("+",luser) == 0) { + (void) pam_get_item(pamh, PAM_USER, (const void **)&user); + _pam_log(LOG_WARNING, "user %s has a `+' user entry", user); + if (opts->opt_promiscuous) + return(1); + /* If not promiscuous we handle it as a negative match */ + return(-1); + } + + /* simple string match */ + return (strcmp(ruser, luser) == 0); +} + +/* + * Returns 1 for blank lines (or only comment lines) and 0 otherwise + */ + +static int __isempty(char *p) +{ + while (*p && isspace(*p)) { + ++p; + } + + return (*p == '\0' || *p == '#') ? 1:0 ; +} + +/* + * Returns 0 if positive match, 1 if _not_ ok. + */ + +static int +__ivaliduser (pam_handle_t *pamh, struct _options *opts, + FILE *hostf, U32 raddr, + const char *luser, const char *ruser, const char *rhost) +{ + register const char *user; + register char *p; + int hcheck, ucheck; + char buf[MAXHOSTNAMELEN + 128]; /* host + login */ + + buf[sizeof (buf)-1] = '\0'; /* terminate line */ + + while (fgets(buf, sizeof(buf), hostf) != NULL) { /* hostf file line */ + p = buf; /* from beginning of file.. */ + + /* Skip empty or comment lines */ + if (__isempty(p)) { + continue; + } + + /* Skip lines that are too long. */ + if (strchr(p, '\n') == NULL) { + int ch = getc(hostf); + + while (ch != '\n' && ch != EOF) + ch = getc(hostf); + continue; + } + + /* + * If there is a hostname at the start of the line. Set it to + * lower case. A leading ' ' or '\t' indicates no hostname + */ + + for (;*p && !isspace(*p); ++p) { + *p = tolower(*p); + } + + /* + * next we want to find the permitted name for the remote user + */ + + if (*p == ' ' || *p == '\t') { + + /* terminate hostname and skip spaces */ + for (*p++='\0'; *p && isspace(*p); ++p); + + user = p; /* this is the user's name */ + while (*p && !isspace(*p)) + ++p; /* find end of user's name */ + } else + user = p; + + *p = '\0'; /* terminate username (+host?) */ + + /* buf -> host(?) ; user -> username(?) */ + + /* First check host part */ + hcheck=__icheckhost(pamh, opts, raddr, buf, rhost); + + if (hcheck<0) + return(1); + + if (hcheck) { + /* Then check user part */ + if (! (*user)) + user = luser; + + ucheck=__icheckuser(pamh, opts, user, ruser, rhost); + + /* Positive 'host user' match? */ + if (ucheck>0) + return(0); + + /* Negative 'host -user' match? */ + if (ucheck<0) + return(1); + + /* Neither, go on looking for match */ + } + } + + return (1); +} + +/* + * New .rhosts strategy: We are passed an ip address. We spin through + * hosts.equiv and .rhosts looking for a match. When the .rhosts only + * has ip addresses, we don't have to trust a nameserver. When it + * contains hostnames, we spin through the list of addresses the nameserver + * gives us and look for a match. + * + * Returns 0 if ok, -1 if not ok. + */ + +static int +pam_iruserok(pam_handle_t *pamh, + struct _options *opts, U32 raddr, int superuser, + const char *ruser, const char *luser, const char *rhost) +{ + const char *cp; + struct stat sbuf; + struct passwd *pwd; + FILE *hostf; + uid_t uid; + int answer; + char pbuf[MAXPATHLEN]; /* potential buffer overrun */ + + if ((!superuser||opts->opt_hosts_equiv_rootok) && !opts->opt_no_hosts_equiv ) { + + /* try to open system hosts.equiv file */ + hostf = fopen (_PATH_HEQUIV, "r"); + if (hostf) { + answer = __ivaliduser(pamh, opts, hostf, raddr, luser + , ruser, rhost); + (void) fclose(hostf); + if (answer == 0) + return 0; /* remote host is equivalent to localhost */ + } /* else { + No hosts.equiv file on system. + } */ + } + + if ( opts->opt_no_rhosts ) + return 1; + + /* + * Identify user's local .rhosts file + */ + + pwd = getpwnam(luser); + if (pwd == NULL) { + /* + * luser is assumed to be valid because of an earlier check for uid = 0 + * we don't log this error twice. However, this shouldn't happen ! + * --cristiang + */ + return(1); + } + + /* check for buffer overrun */ + if (strlen(pwd->pw_dir) + sizeof(USER_RHOSTS_FILE) + 2 >= MAXPATHLEN) { + if (opts->opt_debug) + _pam_log(LOG_DEBUG,"home directory for `%s' is too long", luser); + return 1; /* to dangerous to try */ + } + + (void) strcpy(pbuf, pwd->pw_dir); + (void) strcat(pbuf, USER_RHOSTS_FILE); + + /* + * Change effective uid while _reading_ .rhosts. (not just + * opening). If root and reading an NFS mounted file system, + * can't read files that are 0600 as .rhosts files should be. + */ + + /* We are root, this will not fail */ +#ifdef linux + /* If we are on linux the better way is setfsuid */ + uid = setfsuid(pwd->pw_uid); + hostf = fopen(pbuf, "r"); +#else + uid = geteuid(); + (void) seteuid(pwd->pw_uid); + hostf = fopen(pbuf, "r"); +#endif + + if (hostf == NULL) { + if (opts->opt_debug) + _pam_log(LOG_DEBUG,"Could not open %s file",pbuf); + answer = 1; + goto exit_function; + } + + /* + * If not a regular file, or is owned by someone other than + * user or root or if writeable by anyone but the owner, quit. + */ + + cp = NULL; + if (lstat(pbuf, &sbuf) < 0 || !S_ISREG(sbuf.st_mode)) + cp = ".rhosts not regular file"; + else if (fstat(fileno(hostf), &sbuf) < 0) + cp = ".rhosts fstat failed"; + else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid) + cp = "bad .rhosts owner"; + else if (sbuf.st_mode & S_IWOTH) + cp = ".rhosts writable by other!"; + else if (sbuf.st_mode & S_IWGRP) { + + /* private group caveat */ + if (opts->opt_private_group) { + struct group *grp = getgrgid(sbuf.st_gid); + + if (NULL == grp || NULL == grp->gr_name + || strcmp(luser,grp->gr_name)) { + cp = ".rhosts writable by public group"; + } else if (grp->gr_mem) { + int gcount; + + /* require at most one member (luser) of this group */ + for (gcount=0; grp->gr_mem[gcount]; ++gcount) { + if (strcmp(grp->gr_mem[gcount], luser)) { + gcount = -1; + break; + } + } + if (gcount < 0) { + cp = ".rhosts writable by other members of group"; + } + } + } else { + cp = ".rhosts writable by group"; + } + + } /* It is _NOT_ safe to append an else here... Do so prior to + * S_IWGRP check */ + + /* If there were any problems, quit. */ + if (cp) { + opts->last_error = cp; + answer = 1; + goto exit_function; + } + + answer = __ivaliduser (pamh, opts, hostf, raddr, luser, ruser, rhost); + +exit_function: + /* + * Go here to exit after the fsuid/euid has been adjusted so that + * they are reset before we exit. + */ + +#ifdef linux + setfsuid(uid); +#else + (void)seteuid(uid); +#endif + + if (hostf != NULL) + (void) fclose(hostf); + + return answer; +} + +static int +pam_ruserok (pam_handle_t *pamh, + struct _options *opts, const char *rhost, int superuser, + const char *ruser, const char *luser) +{ + struct hostent *hp; + int answer = 1; /* default to failure */ + U32 *addrs; + int n, i; + + opts->last_error = (char *) 0; + hp = gethostbyname(rhost); /* identify host */ + + if (hp != NULL) { + /* First of all check the address length */ + if (hp->h_length != 4) { + _pam_log(LOG_ALERT, "pam_rhosts module can't work with not IPv4 " + "addresses"); + return 1; /* not allowed */ + } + + /* loop though address list */ + for (n = 0; hp->h_addr_list[n]; n++); + D(("rhosts: %d addresses", n)); + + if (n) { + addrs = calloc (n, hp->h_length); + for (i = 0; i < n; i++) + memcpy (addrs+i, hp->h_addr_list[i], hp->h_length); + + for (i = 0; i < n && answer; i++) { + D(("rhosts: address %d is %04x", i, addrs[i])); + answer = pam_iruserok(pamh, opts, addrs[i], superuser, + ruser, luser, rhost); + /* answer == 0 means success */ + } + + free (addrs); + } + } + + return answer; +} + +/* + * Internal function to do authentication + */ + +static int _pam_auth_rhosts (pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + int retval; + const char *luser; + const char *ruser,*rhost; + struct _options opts; + int as_root = 0; + /* + * Look at the options and set the flags accordingly. + */ + memset (&opts, 0, sizeof (opts)); + set_parameters (&opts, flags, argc, argv); + /* + * Obtain the parameters for the various items + */ + for (;;) { /* abuse loop to avoid goto */ + + /* get the remotehost */ + retval = pam_get_rhost(pamh, &rhost, NULL); + (void) pam_set_item(pamh, PAM_RHOST, rhost); + if (retval != PAM_SUCCESS) { + if (opts.opt_debug) { + _pam_log(LOG_DEBUG, "could not get the remote host name"); + } + break; + } + + /* get the remote user */ + retval = pam_get_ruser(pamh, &ruser, NULL); + (void) pam_set_item(pamh, PAM_RUSER, ruser); + if (retval != PAM_SUCCESS) { + if (opts.opt_debug) + _pam_log(LOG_DEBUG, "could not get the remote username"); + break; + } + + /* get the local user */ + retval = pam_get_user(pamh, &luser, NULL); + + if (retval != PAM_SUCCESS) { + if (opts.opt_debug) + _pam_log(LOG_DEBUG, "could not determine name of local user"); + break; + } + + if (opts.superuser && !strcmp(opts.superuser, luser)) { + as_root = 1; + } + + /* check if the luser uid == 0... --cristiang */ + if (! opts.opt_no_uid_check) { + struct passwd *luser_pwd; + + luser_pwd = getpwnam(luser); + if (luser_pwd == NULL) { + if (opts.opt_debug) + _pam_log(LOG_DEBUG, "user '%s' unknown to this system", + luser); + retval = PAM_AUTH_ERR; + break; + } + if (luser_pwd->pw_uid == 0) + as_root = 1; + luser_pwd = NULL; /* forget */ + } +/* + * Validate the account information. + */ + if (pam_ruserok (pamh, &opts, rhost, as_root, ruser, luser) != 0) { + if ( !opts.opt_suppress ) { + _pam_log(LOG_WARNING, "denied to %s@%s as %s: %s", + ruser, rhost, luser, (opts.last_error==NULL) ? + "access not allowed":opts.last_error); + } + retval = PAM_AUTH_ERR; + } else { + _pam_log(LOG_NOTICE, "allowed to %s@%s as %s", + ruser, rhost, luser); + } + break; + } + + return retval; +} + +/* --- authentication management functions --- */ + +PAM_EXTERN +int pam_sm_authenticate (pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + int retval; + + if (sizeof(U32) != 4) { + _pam_log (LOG_ALERT, "pam_rhosts module can\'t work on this hardware " + "(yet)"); + return PAM_AUTH_ERR; + } + sethostent(1); + retval = _pam_auth_rhosts (pamh, flags, argc, argv); + endhostent(); + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + +/* end of module definition */ + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_rhosts_auth_modstruct = { + "pam_rhosts_auth", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* + * $Log$ + * Revision 1.1 2000/06/20 22:11:56 agmorgan + * Initial revision + * + * Revision 1.4 1999/11/08 05:46:58 morgan + * fixes for .71 + * + * Revision 1.3 1999/10/09 05:12:49 morgan + * added hosts_equiv_rootok support + * + * Revision 1.2 1998/12/14 05:47:58 morgan + * added a couple of options: specify the name of root, and don't do the + * user-known check. + * + * Revision 1.1.1.1 1998/07/12 05:17:16 morgan + * Linux PAM sources pre-0.66 + * + * Revision 1.12 1997/09/27 14:34:01 morgan + * fixed comment and renamed iruserok to pam_iruserok. + * + * Revision 1.11 1997/04/05 06:26:39 morgan + * fairly major fixes and enhancements (see CHANGELOG for 0.57 release) + * + * Revision 1.10 1997/02/09 02:09:30 morgan + * - implementation of 'debug' argument (Cristian Gafton) + * - we check for uid=0 accounts instead of hardcoded 'root' (Cristian Gafton) + * + * Revision 1.9 1996/12/01 03:09:47 morgan + * *** empty log message *** + * + * Revision 1.8 1996/11/12 06:08:59 morgan + * Oliver Crow's "rootok" patch plus a little clean up of set_option + * (AGM) + * + * Revision 1.7 1996/11/10 20:15:56 morgan + * cross platform support + * + * Revision 1.6 1996/08/09 05:46:29 morgan + * removed code for manually setting the remote username etc.. + * + */ diff --git a/modules/pam_rootok/.cvsignore b/modules/pam_rootok/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_rootok/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_rootok/Makefile b/modules/pam_rootok/Makefile new file mode 100644 index 00000000..f3f2e53d --- /dev/null +++ b/modules/pam_rootok/Makefile @@ -0,0 +1,117 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:56 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.7 1997/04/05 06:25:20 morgan +# fakeroot +# +# Revision 1.6 1997/02/15 19:15:50 morgan +# fixed email +# +# Revision 1.5 1996/11/10 20:16:10 morgan +# cross platform support +# +# Revision 1.4 1996/09/05 06:29:36 morgan +# ld --> gcc +# +# Revision 1.3 1996/05/26 15:47:46 morgan +# make dynamic/static dirs! +# +# Revision 1.2 1996/05/26 04:04:53 morgan +# automated static support +# +# Revision 1.1 1996/05/05 17:14:15 morgan +# Initial revision +# +# +# Created by Andrew Morgan 1996/5/5 +# + +TITLE=pam_rootok + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_rootok/README b/modules/pam_rootok/README new file mode 100644 index 00000000..7120b164 --- /dev/null +++ b/modules/pam_rootok/README @@ -0,0 +1,18 @@ +# $Id$ +# + +this module is an authentication module that performs one task: if the +id of the user is '0' then it returns 'PAM_SUCCESS' with the +'sufficient' /etc/pam.conf control flag it can be used to allow +password free access to some service for 'root' + +Recognized arguments: + + debug write a message to syslog indicating success or + failure. + +module services provided: + + auth _authetication and _setcred (blank) + +Andrew Morgan diff --git a/modules/pam_rootok/pam_rootok.c b/modules/pam_rootok/pam_rootok.c new file mode 100644 index 00000000..a275e6eb --- /dev/null +++ b/modules/pam_rootok/pam_rootok.c @@ -0,0 +1,109 @@ +/* pam_rootok module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-rootok", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + + +/* argument parsing */ + +#define PAM_DEBUG_ARG 01 + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int ctrl; + int retval = PAM_AUTH_ERR; + + ctrl = _pam_parse(argc, argv); + if (getuid() == 0) + retval = PAM_SUCCESS; + + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_DEBUG, "authetication %s" + , retval==PAM_SUCCESS ? "succeeded":"failed" ); + } + + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_rootok_modstruct = { + "pam_rootok", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_securetty/.cvsignore b/modules/pam_securetty/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_securetty/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_securetty/Makefile b/modules/pam_securetty/Makefile new file mode 100644 index 00000000..d8a09ea1 --- /dev/null +++ b/modules/pam_securetty/Makefile @@ -0,0 +1,83 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# + +TITLE=pam_securetty + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< diff --git a/modules/pam_securetty/README b/modules/pam_securetty/README new file mode 100644 index 00000000..1df095c9 --- /dev/null +++ b/modules/pam_securetty/README @@ -0,0 +1,9 @@ +pam_securetty: + Allows root logins only if the user is logging in on a + "secure" tty, as defined by the listing in /etc/securetty + + Also checks to make sure that /etc/securetty is a plain + file and not world writable. + + - Elliot Lee , Red Hat Software. + July 25, 1996. diff --git a/modules/pam_securetty/pam_securetty.c b/modules/pam_securetty/pam_securetty.c new file mode 100644 index 00000000..9e6121e8 --- /dev/null +++ b/modules/pam_securetty/pam_securetty.c @@ -0,0 +1,191 @@ +/* pam_securetty module */ + +#define SECURETTY_FILE "/etc/securetty" +#define TTY_PREFIX "/dev/" + +/* + * by Elliot Lee , Red Hat Software. + * July 25, 1996. + * This code shamelessly ripped from the pam_rootok module. + * Slight modifications AGM. 1996/12/3 + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAM_SM_AUTH + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-securetty", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 0x0001 + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int retval = PAM_AUTH_ERR; + const char *username; + char *uttyname; + char ttyfileline[256]; + struct stat ttyfileinfo; + struct passwd *user_pwd; + FILE *ttyfile; + int ctrl; + + /* parse the arguments */ + ctrl = _pam_parse(argc, argv); + + retval = pam_get_user(pamh, &username, NULL); + if (retval != PAM_SUCCESS || username == NULL) { + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_WARNING, "cannot determine username"); + } + return (retval == PAM_CONV_AGAIN + ? PAM_INCOMPLETE:PAM_SERVICE_ERR); + } + + retval = pam_get_item(pamh, PAM_TTY, (const void **)&uttyname); + if (retval != PAM_SUCCESS || uttyname == NULL) { + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_WARNING, "cannot determine user's tty"); + } + return PAM_SERVICE_ERR; + } + + /* The PAM_TTY item may be prefixed with "/dev/" - skip that */ + if (strncmp(TTY_PREFIX, uttyname, sizeof(TTY_PREFIX)-1) == 0) + uttyname += sizeof(TTY_PREFIX)-1; + + user_pwd = getpwnam(username); + if (user_pwd == NULL) { + return PAM_IGNORE; + } else if (user_pwd->pw_uid != 0) { /* If the user is not root, + securetty's does not apply + to them */ + return PAM_SUCCESS; + } + + if (stat(SECURETTY_FILE, &ttyfileinfo)) { + _pam_log(LOG_NOTICE, "Couldn't open " SECURETTY_FILE); + return PAM_SUCCESS; /* for compatibility with old securetty handling, + this needs to succeed. But we still log the + error. */ + } + + if ((ttyfileinfo.st_mode & S_IWOTH) + || !S_ISREG(ttyfileinfo.st_mode)) { + /* If the file is world writable or is not a + normal file, return error */ + _pam_log(LOG_ERR, SECURETTY_FILE + " is either world writable or not a normal file"); + return PAM_AUTH_ERR; + } + + ttyfile = fopen(SECURETTY_FILE,"r"); + if(ttyfile == NULL) { /* Check that we opened it successfully */ + _pam_log(LOG_ERR, + "Error opening " SECURETTY_FILE); + return PAM_SERVICE_ERR; + } + /* There should be no more errors from here on */ + retval=PAM_AUTH_ERR; + /* This loop assumes that PAM_SUCCESS == 0 + and PAM_AUTH_ERR != 0 */ + while((fgets(ttyfileline,sizeof(ttyfileline)-1, ttyfile) != NULL) + && retval) { + if(ttyfileline[strlen(ttyfileline) - 1] == '\n') + ttyfileline[strlen(ttyfileline) - 1] = '\0'; + retval = strcmp(ttyfileline,uttyname); + } + fclose(ttyfile); + if(retval) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_WARNING, "access denied: tty '%s' is not secure !", + uttyname); + retval = PAM_AUTH_ERR; + } + if ((retval == PAM_SUCCESS) && (ctrl & PAM_DEBUG_ARG)) + _pam_log(LOG_DEBUG, "access allowed for '%s' on '%s'", + username, uttyname); + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_securetty_modstruct = { + "pam_securetty", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_shells/.cvsignore b/modules/pam_shells/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_shells/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_shells/Makefile b/modules/pam_shells/Makefile new file mode 100644 index 00000000..121b19a0 --- /dev/null +++ b/modules/pam_shells/Makefile @@ -0,0 +1,84 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# + +TITLE=pam_shells + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_shells/README b/modules/pam_shells/README new file mode 100644 index 00000000..cbd5bfb5 --- /dev/null +++ b/modules/pam_shells/README @@ -0,0 +1,10 @@ +pam_shells: + Authentication is granted if the users shell is listed in + /etc/shells. If no shell is in /etc/passwd (empty), the + /bin/sh is used (following ftpd's convention). + + Also checks to make sure that /etc/shells is a plain + file and not world writable. + + - Erik Troan , Red Hat Software. + August 5, 1996. diff --git a/modules/pam_shells/pam_shells.c b/modules/pam_shells/pam_shells.c new file mode 100644 index 00000000..f460ee5f --- /dev/null +++ b/modules/pam_shells/pam_shells.c @@ -0,0 +1,133 @@ +/* pam_shells module */ + +#define SHELL_FILE "/etc/shells" + +/* + * by Erik Troan , Red Hat Software. + * August 5, 1996. + * This code shamelessly ripped from the pam_securetty module. + */ + +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-shells", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int retval = PAM_AUTH_ERR; + const char *userName; + char *userShell; + char shellFileLine[256]; + struct stat sb; + struct passwd * pw; + FILE * shellFile; + + retval = pam_get_user(pamh,&userName,NULL); + if(retval != PAM_SUCCESS) + return PAM_SERVICE_ERR; + + if(!userName || (strlen(userName) <= 0)) { + /* Don't let them use a NULL username... */ + pam_get_user(pamh,&userName,NULL); + if (retval != PAM_SUCCESS) + return PAM_SERVICE_ERR; + } + + pw = getpwnam(userName); + if (!pw) + return PAM_AUTH_ERR; /* user doesn't exist */ + userShell = pw->pw_shell; + + if(stat(SHELL_FILE,&sb)) { + _pam_log(LOG_ERR, SHELL_FILE, " cannot be stat'd (it probably does " + "not exist)"); + return PAM_AUTH_ERR; /* must have /etc/shells */ + } + + if((sb.st_mode & S_IWOTH) || !S_ISREG(sb.st_mode)) { + _pam_log(LOG_ERR, + SHELL_FILE " is either world writable or not a normal file"); + return PAM_AUTH_ERR; + } + + shellFile = fopen(SHELL_FILE,"r"); + if(shellFile == NULL) { /* Check that we opened it successfully */ + _pam_log(LOG_ERR, + "Error opening " SHELL_FILE); + return PAM_SERVICE_ERR; + } + /* There should be no more errors from here on */ + retval=PAM_AUTH_ERR; + /* This loop assumes that PAM_SUCCESS == 0 + and PAM_AUTH_ERR != 0 */ + while((fgets(shellFileLine,255,shellFile) != NULL) + && retval) { + if (shellFileLine[strlen(shellFileLine) - 1] == '\n') + shellFileLine[strlen(shellFileLine) - 1] = '\0'; + retval = strcmp(shellFileLine, userShell); + } + fclose(shellFile); + if(retval) + retval = PAM_AUTH_ERR; + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_shells_modstruct = { + "pam_shells", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_stress/.cvsignore b/modules/pam_stress/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_stress/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_stress/Makefile b/modules/pam_stress/Makefile new file mode 100644 index 00000000..1bcfa502 --- /dev/null +++ b/modules/pam_stress/Makefile @@ -0,0 +1,115 @@ +# +# $Id$ +# +# Created by Andrew Morgan 1996/3/11 +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:11:57 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.7 1997/04/05 06:23:08 morgan +# fakeroot +# +# Revision 1.6 1997/02/15 19:05:55 morgan +# fixed email +# +# Revision 1.5 1996/11/10 20:17:55 morgan +# cross platform support +# +# Revision 1.4 1996/09/05 06:31:09 morgan +# ld --> gcc +# +# Revision 1.3 1996/05/26 15:50:43 morgan +# make dynamic and static dirs +# +# Revision 1.2 1996/05/26 04:11:56 morgan +# automated static support +# +# +# + +TITLE=pam_stress + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_stress/README b/modules/pam_stress/README new file mode 100644 index 00000000..74a297b2 --- /dev/null +++ b/modules/pam_stress/README @@ -0,0 +1,66 @@ +# +# $Id$ +# +# This describes the behavior of this module with respect to the +# /etc/pam.conf file. +# +# writen by Andrew Morgan +# + +This module recognizes the following arguments. + +debug put lots of information in syslog. + *NOTE* this option writes passwords to syslog, so + don't use anything sensitive when testing. + +no_warn don't give warnings about things (otherwise warnings are issued + via the conversation function) + +use_first_pass don't prompt for a password, for pam_sm_authentication + function just use item PAM_AUTHTOK. + +try_first_pass don't prompt for a password unless there has been no + previous authentication token (item PAM_AUTHTOK is NULL) + +rootok This is intended for the pam_sm_chauthtok function and + it instructs this function to permit root to change + the user's password without entering the old password. + +The following arguments are acted on by the module. They are intended +to make the module give the impression of failing as a fully +functioning module might. + +expired an argument intended for the account and chauthtok module + parts. It instructs the module to act as if the user's + password has expired + +fail_1 this instructs the module to make its first function fail. + +fail_2 this instructs the module to make its second function (if there + is one) fail. + + The function break up is indicated in the Module + Developers' Guide. Listed here it is: + + service function 1 function 2 + ------- ---------- ---------- + auth pam_sm_authenticate pam_sm_setcred + password pam_sm_chauthtok + session pam_sm_open_session pam_sm_close_session + account pam_sm_acct_mgmt + +prelim for pam_sm_chauthtok, means fail on PAM_PRELIM_CHECK. + +required for pam_sm_chauthtok, means fail if the user hasn't already + been authenticated by this module. (See stress_new_pwd data + item below.) + +# +# data strings that this module uses are the following: +# + +data name value(s) Comments +--------- -------- -------- +stress_new_pwd yes tells pam_sm_chauthtok that + pam_sm_acct_mgmt says we need a new + password diff --git a/modules/pam_stress/pam_stress.c b/modules/pam_stress/pam_stress.c new file mode 100644 index 00000000..e5813579 --- /dev/null +++ b/modules/pam_stress/pam_stress.c @@ -0,0 +1,565 @@ +/* pam_stress module */ + +/* $Id$ + * + * created by Andrew Morgan 1996/3/12 + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_SESSION +#define PAM_SM_PASSWORD + +#include +#include + +static char *_strdup(const char *x) +{ + char *new; + new = malloc(strlen(x)+1); + strcpy(new,x); + return new; +} + +/* log errors */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-stress", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* ---------- */ + +/* an internal function to turn all possible test arguments into bits + of a ctrl number */ + +/* generic options */ + +#define PAM_ST_DEBUG 01 +#define PAM_ST_NO_WARN 02 +#define PAM_ST_USE_PASS1 04 +#define PAM_ST_TRY_PASS1 010 +#define PAM_ST_ROOTOK 020 + +/* simulation options */ + +#define PAM_ST_EXPIRED 040 +#define PAM_ST_FAIL_1 0100 +#define PAM_ST_FAIL_2 0200 +#define PAM_ST_PRELIM 0400 +#define PAM_ST_REQUIRE_PWD 01000 + +/* some syslogging */ + +static void _pam_report(int ctrl, const char *name, int flags, + int argc, const char **argv) +{ + if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG, "CALLED: %s", name); + _pam_log(LOG_DEBUG, "FLAGS : 0%o%s", flags, + (flags & PAM_SILENT) ? " (silent)":""); + _pam_log(LOG_DEBUG, "CTRL = 0%o",ctrl); + _pam_log(LOG_DEBUG, "ARGV :"); + while (argc--) { + _pam_log(LOG_DEBUG, " \"%s\"", *argv++); + } + } +} + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_ST_DEBUG; + else if (!strcmp(*argv,"no_warn")) + ctrl |= PAM_ST_NO_WARN; + else if (!strcmp(*argv,"use_first_pass")) + ctrl |= PAM_ST_USE_PASS1; + else if (!strcmp(*argv,"try_first_pass")) + ctrl |= PAM_ST_TRY_PASS1; + else if (!strcmp(*argv,"rootok")) + ctrl |= PAM_ST_ROOTOK; + + /* simulation options */ + + else if (!strcmp(*argv,"expired")) /* signal password needs + renewal */ + ctrl |= PAM_ST_EXPIRED; + else if (!strcmp(*argv,"fail_1")) /* instruct fn 1 to fail */ + ctrl |= PAM_ST_FAIL_1; + else if (!strcmp(*argv,"fail_2")) /* instruct fn 2 to fail */ + ctrl |= PAM_ST_FAIL_2; + else if (!strcmp(*argv,"prelim")) /* instruct pam_sm_setcred + to fail on first call */ + ctrl |= PAM_ST_PRELIM; + else if (!strcmp(*argv,"required")) /* module is fussy about the + user being authenticated */ + ctrl |= PAM_ST_REQUIRE_PWD; + + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +static int converse(pam_handle_t *pamh, int nargs + , struct pam_message **message + , struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + if ((retval = pam_get_item(pamh,PAM_CONV,(const void **)&conv)) + == PAM_SUCCESS) { + retval = conv->conv(nargs, (const struct pam_message **) message + , response, conv->appdata_ptr); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR,"(pam_stress) converse returned %d",retval); + _pam_log(LOG_ERR,"that is: %s",pam_strerror(pamh, retval)); + } + } else { + _pam_log(LOG_ERR,"(pam_stress) converse failed to get pam_conv"); + } + + return retval; +} + +/* authentication management functions */ + +static int stress_get_password(pam_handle_t *pamh, int flags + , int ctrl, char **password) +{ + char *pass; + + if ( (ctrl & (PAM_ST_TRY_PASS1|PAM_ST_USE_PASS1)) + && (pam_get_item(pamh,PAM_AUTHTOK,(const void **)&pass) + == PAM_SUCCESS) + && (pass != NULL) ) { + pass = _strdup(pass); + } else if ((ctrl & PAM_ST_USE_PASS1)) { + _pam_log(LOG_WARNING, "pam_stress: no forwarded password"); + return PAM_PERM_DENIED; + } else { /* we will have to get one */ + struct pam_message msg[1],*pmsg[1]; + struct pam_response *resp; + int retval; + + /* set up conversation call */ + + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + msg[0].msg = "STRESS Password: "; + resp = NULL; + + if ((retval = converse(pamh,1,pmsg,&resp)) != PAM_SUCCESS) { + return retval; + } + + if (resp) { + if ((resp[0].resp == NULL) && (ctrl & PAM_ST_DEBUG)) { + _pam_log(LOG_DEBUG, + "pam_sm_authenticate: NULL authtok given"); + } + if ((flags & PAM_DISALLOW_NULL_AUTHTOK) + && resp[0].resp == NULL) { + free(resp); + return PAM_AUTH_ERR; + } + + pass = resp[0].resp; /* remember this! */ + + resp[0].resp = NULL; + } else if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG,"pam_sm_authenticate: no error reported"); + _pam_log(LOG_DEBUG,"getting password, but NULL returned!?"); + return PAM_CONV_ERR; + } + free(resp); + } + + *password = pass; /* this *MUST* be free()'d by this module */ + + return PAM_SUCCESS; +} + +/* function to clean up data items */ + +static void wipe_up(pam_handle_t *pamh, void *data, int error) +{ + free(data); +} + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + int retval=PAM_SUCCESS; + char *pass; + int ctrl; + + D(("called.")); + + ctrl = _pam_parse(argc,argv); + _pam_report(ctrl, "pam_sm_authenticate", flags, argc, argv); + + /* try to get the username */ + + retval = pam_get_user(pamh, &username, "username: "); + if ((ctrl & PAM_ST_DEBUG) && (retval == PAM_SUCCESS)) { + _pam_log(LOG_DEBUG, "pam_sm_authenticate: username = %s", username); + } else if (retval != PAM_SUCCESS) { + _pam_log(LOG_WARNING, "pam_sm_authenticate: failed to get username"); + return retval; + } + + /* now get the password */ + + retval = stress_get_password(pamh,flags,ctrl,&pass); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_WARNING, "pam_sm_authenticate: " + "failed to get a password"); + return retval; + } + + /* try to set password item */ + + retval = pam_set_item(pamh,PAM_AUTHTOK,pass); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_WARNING, "pam_sm_authenticate: " + "failed to store new password"); + _pam_overwrite(pass); + free(pass); + return retval; + } + + /* clean up local copy of password */ + + _pam_overwrite(pass); + free(pass); + pass = NULL; + + /* if we are debugging then we print the password */ + + if (ctrl & PAM_ST_DEBUG) { + (void) pam_get_item(pamh,PAM_AUTHTOK,(const void **)&pass); + _pam_log(LOG_DEBUG, + "pam_st_authenticate: password entered is: [%s]\n",pass); + pass = NULL; + } + + /* if we signal a fail for this function then fail */ + + if ((ctrl & PAM_ST_FAIL_1) && retval == PAM_SUCCESS) + return PAM_PERM_DENIED; + + return retval; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int ctrl = _pam_parse(argc,argv); + + D(("called. [post parsing]")); + + _pam_report(ctrl, "pam_sm_setcred", flags, argc, argv); + + if (ctrl & PAM_ST_FAIL_2) + return PAM_CRED_ERR; + + return PAM_SUCCESS; +} + +/* account management functions */ + +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int ctrl = _pam_parse(argc,argv); + + D(("called. [post parsing]")); + + _pam_report(ctrl,"pam_sm_acct_mgmt", flags, argc, argv); + + if (ctrl & PAM_ST_FAIL_1) + return PAM_PERM_DENIED; + else if (ctrl & PAM_ST_EXPIRED) { + void *text = malloc(sizeof("yes")+1); + strcpy(text,"yes"); + pam_set_data(pamh,"stress_new_pwd",text,wipe_up); + if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG,"pam_sm_acct_mgmt: need a new password"); + } + return PAM_NEW_AUTHTOK_REQD; + } + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + char *username,*service; + int ctrl = _pam_parse(argc,argv); + + D(("called. [post parsing]")); + + _pam_report(ctrl,"pam_sm_open_session", flags, argc, argv); + + if ((pam_get_item(pamh, PAM_USER, (const void **) &username) + != PAM_SUCCESS) + || (pam_get_item(pamh, PAM_SERVICE, (const void **) &service) + != PAM_SUCCESS)) { + _pam_log(LOG_WARNING,"pam_sm_open_session: for whom?"); + return PAM_SESSION_ERR; + } + + _pam_log(LOG_NOTICE,"pam_stress: opened [%s] session for user [%s]" + , service, username); + + if (ctrl & PAM_ST_FAIL_1) + return PAM_SESSION_ERR; + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username,*service; + int ctrl = _pam_parse(argc,argv); + + D(("called. [post parsing]")); + + _pam_report(ctrl,"pam_sm_close_session", flags, argc, argv); + + if ((pam_get_item(pamh, PAM_USER, (const void **)&username) + != PAM_SUCCESS) + || (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) + != PAM_SUCCESS)) { + _pam_log(LOG_WARNING,"pam_sm_close_session: for whom?"); + return PAM_SESSION_ERR; + } + + _pam_log(LOG_NOTICE,"pam_stress: closed [%s] session for user [%s]" + , service, username); + + if (ctrl & PAM_ST_FAIL_2) + return PAM_SESSION_ERR; + + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int retval; + int ctrl = _pam_parse(argc,argv); + + D(("called. [post parsing]")); + + _pam_report(ctrl,"pam_sm_chauthtok", flags, argc, argv); + + /* this function should be called twice by the Linux-PAM library */ + + if (flags & PAM_PRELIM_CHECK) { /* first call */ + if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG,"pam_sm_chauthtok: prelim check"); + } + if (ctrl & PAM_ST_PRELIM) + return PAM_TRY_AGAIN; + + return PAM_SUCCESS; + } else if (flags & PAM_UPDATE_AUTHTOK) { /* second call */ + struct pam_message msg[3],*pmsg[3]; + struct pam_response *resp; + const char *text; + char *txt=NULL; + int i; + + if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG,"pam_sm_chauthtok: alter password"); + } + + if (ctrl & PAM_ST_FAIL_1) + return PAM_AUTHTOK_LOCK_BUSY; + + if ( !(ctrl && PAM_ST_EXPIRED) + && (flags & PAM_CHANGE_EXPIRED_AUTHTOK) + && (pam_get_data(pamh,"stress_new_pwd",(const void **)&text) + != PAM_SUCCESS || strcmp(text,"yes"))) { + return PAM_SUCCESS; /* the token has not expired */ + } + + /* the password should be changed */ + + if ((ctrl & PAM_ST_REQUIRE_PWD) + && !(getuid() == 0 && (ctrl & PAM_ST_ROOTOK)) + ) { /* first get old one? */ + char *pass; + + if (ctrl & PAM_ST_DEBUG) { + _pam_log(LOG_DEBUG + ,"pam_sm_chauthtok: getting old password"); + } + retval = stress_get_password(pamh,flags,ctrl,&pass); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_DEBUG + ,"pam_sm_chauthtok: no password obtained"); + return retval; + } + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, pass); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_DEBUG + ,"pam_sm_chauthtok: could not set OLDAUTHTOK"); + _pam_overwrite(pass); + free(pass); + return retval; + } + _pam_overwrite(pass); + free(pass); + } + + /* set up for conversation */ + + if (!(flags & PAM_SILENT)) { + char *username; + + if ( pam_get_item(pamh, PAM_USER, (const void **)&username) + || username == NULL ) { + _pam_log(LOG_ERR,"no username set"); + return PAM_USER_UNKNOWN; + } + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; +#define _LOCAL_STRESS_COMMENT "Changing STRESS password for " + txt = (char *) malloc(sizeof(_LOCAL_STRESS_COMMENT) + +strlen(username)+1); + strcpy(txt, _LOCAL_STRESS_COMMENT); +#undef _LOCAL_STRESS_COMMENT + strcat(txt, username); + msg[0].msg = txt; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = "Enter new STRESS password: "; + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = "Retype new STRESS password: "; + resp = NULL; + + retval = converse(pamh,i,pmsg,&resp); + if (txt) { + free(txt); + txt = NULL; /* clean up */ + } + if (retval != PAM_SUCCESS) { + return retval; + } + + if (resp == NULL) { + _pam_log(LOG_ERR, "pam_sm_chauthtok: no response from conv"); + return PAM_CONV_ERR; + } + + /* store the password */ + + if (resp[i-2].resp && resp[i-1].resp) { + if (strcmp(resp[i-2].resp,resp[i-1].resp)) { + /* passwords are not the same; forget and return error */ + + _pam_drop_reply(resp, i); + + if (!(flags & PAM_SILENT) && !(ctrl & PAM_ST_NO_WARN)) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_ERROR_MSG; + msg[0].msg = "Verification mis-typed; " + "password unchaged"; + resp = NULL; + (void) converse(pamh,1,pmsg,&resp); + if (resp) { + _pam_drop_reply(resp, 1); + } + } + return PAM_AUTHTOK_ERR; + } + + if (pam_get_item(pamh,PAM_AUTHTOK,(const void **)&text) + == PAM_SUCCESS) { + (void) pam_set_item(pamh,PAM_OLDAUTHTOK,text); + text = NULL; + } + (void) pam_set_item(pamh,PAM_AUTHTOK,resp[0].resp); + } else { + _pam_log(LOG_DEBUG,"pam_sm_chauthtok: problem with resp"); + retval = PAM_SYSTEM_ERR; + } + + _pam_drop_reply(resp, i); /* clean up the passwords */ + } else { + _pam_log(LOG_ERR,"pam_sm_chauthtok: this must be a Linux-PAM error"); + return PAM_SYSTEM_ERR; + } + + return retval; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_stress_modstruct = { + "pam_stress", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok +}; + +#endif diff --git a/modules/pam_tally/.cvsignore b/modules/pam_tally/.cvsignore new file mode 100644 index 00000000..e1a4f48f --- /dev/null +++ b/modules/pam_tally/.cvsignore @@ -0,0 +1,2 @@ +dynamic +pam_tally diff --git a/modules/pam_tally/Makefile b/modules/pam_tally/Makefile new file mode 100644 index 00000000..78ce621d --- /dev/null +++ b/modules/pam_tally/Makefile @@ -0,0 +1,109 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module and +# application for Linux-PAM. You should not modify this Makefile +# (unless you know what you are doing!). +# +# + +TITLE=pam_tally + +# +## Additional rules for making (and moving) the application added. +## Assuming that all modules' applications are called $TITLE +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +APPSRC = $(TITLE)_app.c +APPOBJ = $(TITLE)_app.o +APPOBJD = $(addprefix dynamic/,$(APPOBJ)) +APPOBJS = $(addprefix static/,$(APPOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +APPLICATION = $(TITLE) +APPMODE = 755 + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register +# we're not yet in a position to build this. If you want it, build it +# separately... +# $(APPLICATION) + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) + +$(APPLICATION): $(APPOBJD) + $(CC) $(CFLAGS) -o $@ $< $(LOADLIBES) + +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) + +$(APPLICATION): $(APPOBJS) + $(CC) $(CFLAGS) -o $@ $< $(LOADLIBES) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SUPLEMENTED) +# $(INSTALL) -m $(APPMODE) $(APPLICATION) $(FAKEROOT)$(SUPLEMENTED) + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(SUPLEMENTED)/$(TITLE) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) $(APPOBJD) $(APPOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* $(APPLICATION) + +.c.o: + $(CC) $(CFLAGS) -c $< diff --git a/modules/pam_tally/README b/modules/pam_tally/README new file mode 100644 index 00000000..b58b24e4 --- /dev/null +++ b/modules/pam_tally/README @@ -0,0 +1,95 @@ +SUMMARY: + pam_tally: + + Maintains a count of attempted accesses, can reset count on success, + can deny access if too many attempts fail. + + Options: + + * onerr=[succeed|fail] (if something weird happens + such as unable to open the file, what to do?) + * file=/where/to/keep/counts (default /var/log/faillog) + + (auth) + Authentication phase increments attempted login counter. + * no_magic_root (root DOES increment counter. Use for + daemon-based stuff, like telnet/rsh/login) + + (account) + Account phase can deny access and/or reset attempts counter. + * deny=n (deny access if tally for this user exceeds n; + The presence of deny=n changes the default for + reset/no_reset to reset, unless the user trying to + gain access is root and the no_magic_root option + has NOT been specified.) + + * no_magic_root (access attempts by root DON'T ignore deny. + Use this for daemon-based stuff, like telnet/rsh/login) + * even_deny_root_account (Root can become unavailable. BEWARE. + Note that magic root trying to gain root bypasses this, + but normal users can be locked out.) + + * reset (reset count to 0 on successful entry, even for + magic root) + * no_reset (don't reset count on successful entry) + This is the default unless deny exists and the + user attempting access is NOT magic root. + + * per_user (If /var/log/faillog contains a non-zero + .fail_max field for this user then use it + instead of deny=n parameter) + + * no_lock_time (Don't use .fail_locktime filed in + /var/log/faillog for this user) + + Also checks to make sure that the counts file is a plain + file and not world writable. + + - Tim Baverstock , v0.1 5 March 1997 + +LONGER: + +pam_tally comes in two parts: pam_tally.so and pam_tally. + +pam_tally.so sits in a pam config file, in the auth and account sections. + +In the auth section, it increments a per-uid counter for each attempted +login, in the account section, it denies access if attempted logins +exceed some threashold and/or resets that counter to zero on successful +login. + +Root is treated specially: + +1. When a process already running as root tries to access some service, the +access is `magic', and bypasses pam_tally's checks: handy for `su'ing from +root into an account otherwise blocked. However, for services like telnet or +login which always effectively run from the root account, root (ie everyone) +shouldn't be granted this magic status, and the flag `no_magic_root' should +be set in this situation, as noted in the summary above. [This option may +be obsolete, with `sufficient root' processing.] + +2. Normally, failed attempts to access root will NOT cause the root +account to become blocked, to prevent denial-of-service: if your users aren't +given shell accounts and root may only login via `su' or at the machine +console (not telnet/rsh, etc), this is safe. If you really want root to be +blocked for some given service, use even_deny_root_account. + +pam_tally is an (optional) application which can be used to interrogate and +manipulate the counter file. It can display users' counts, set individual +counts, or clear all counts. Setting artificially high counts may be useful +for blocking users without changing their passwords. I found it useful to +clear all counts every midnight from a cron.. + +The counts file is organised as a binary-word array, indexed by uid. You +can probably make sense of it with `od', if you don't want to use the +supplied appliction. + +BUGS: + +pam_tally is very dependant on getpw*(): a database of usernames +would be much more flexible. + +The (4.0 Redhat) utilities seem to do funny things with uid, and I'm +not wholly sure I understood what I should have been doing anyway so +the `keep a count of current logins' bit has been #ifdef'd out and you +can only reset the counter on successful authentication, for now. diff --git a/modules/pam_tally/faillog.h b/modules/pam_tally/faillog.h new file mode 100644 index 00000000..7f704713 --- /dev/null +++ b/modules/pam_tally/faillog.h @@ -0,0 +1,55 @@ +/* + * Copyright 1989 - 1994, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * faillog.h - login failure logging file format + * + * $Id$ + * + * The login failure file is maintained by login(1) and faillog(8) + * Each record in the file represents a separate UID and the file + * is indexed in that fashion. + */ + +#ifndef _FAILLOG_H +#define _FAILLOG_H + +struct faillog { + short fail_cnt; /* failures since last success */ + short fail_max; /* failures before turning account off */ + char fail_line[12]; /* last failure occured here */ + time_t fail_time; /* last failure occured then */ + /* + * If nonzero, the account will be re-enabled if there are no + * failures for fail_locktime seconds since last failure. + */ + long fail_locktime; +}; + +#endif diff --git a/modules/pam_tally/pam_tally.c b/modules/pam_tally/pam_tally.c new file mode 100644 index 00000000..ff75697d --- /dev/null +++ b/modules/pam_tally/pam_tally.c @@ -0,0 +1,689 @@ +/* + * pam_tally.c + * + * $Id$ + */ + + +/* By Tim Baverstock , Multi Media Machine Ltd. + * 5 March 1997 + * + * Stuff stolen from pam_rootok and pam_listfile + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "faillog.h" + +#ifndef TRUE +#define TRUE 1L +#define FALSE 0L +#endif + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +/* #define PAM_SM_SESSION */ +/* #define PAM_SM_PASSWORD */ + +#include + +/*---------------------------------------------------------------------*/ + +#define DEFAULT_LOGFILE "/var/log/faillog" +#define MODULE_NAME "pam_tally" + +enum TALLY_RESET { + TALLY_RESET_DEFAULT, + TALLY_RESET_RESET, + TALLY_RESET_NO_RESET +}; + +#define tally_t unsigned short int +#define TALLY_FMT "%hu" +#define TALLY_HI ((tally_t)~0L) + +#define UID_FMT "%hu" + +#ifndef FILENAME_MAX +# define FILENAME_MAX MAXPATHLEN +#endif + +static struct faillog faillog; +static time_t fail_time; + +/*---------------------------------------------------------------------*/ + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef MAIN + vfprintf(stderr,format,args); +#else + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + closelog(); +#endif + va_end(args); +} + +/*---------------------------------------------------------------------*/ + +/* --- Support function: get uid (and optionally username) from PAM or + cline_user --- */ + +#ifdef MAIN +static char *cline_user=0; /* cline_user is used in the administration prog */ +#endif + +static int pam_get_uid( pam_handle_t *pamh, uid_t *uid, const char **userp ) + { + const char *user; + struct passwd *pw; + +#ifdef MAIN + user = cline_user; +#else + pam_get_user( pamh, &user, NULL ); +#endif + + if ( !user || !*user ) { + _pam_log(LOG_ERR, MODULE_NAME ": pam_get_uid; user?"); + return PAM_AUTH_ERR; + } + + if ( ! ( pw = getpwnam( user ) ) ) { + _pam_log(LOG_ERR,MODULE_NAME ": pam_get_uid; no such user %s",user); + return PAM_USER_UNKNOWN; + } + + if ( uid ) *uid = pw->pw_uid; + if ( userp ) *userp = user; + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- Support function: open/create tallyfile and return tally for uid --- */ + +/* If on entry *tally==TALLY_HI, tallyfile is opened READONLY */ +/* Otherwise, if on entry tallyfile doesn't exist, creation is attempted. */ + +static int get_tally( tally_t *tally, + uid_t uid, + const char *filename, + FILE **TALLY ) + { + struct stat fileinfo; + int lstat_ret = lstat(filename,&fileinfo); + + if ( lstat_ret && *tally!=TALLY_HI ) { + int oldmask = umask(077); + *TALLY=fopen(filename, "a"); + /* Create file, or append-open in pathological case. */ + umask(oldmask); + if ( !*TALLY ) { + _pam_log(LOG_ALERT, "Couldn't create %s",filename); + return PAM_AUTH_ERR; + } + lstat_ret = fstat(fileno(*TALLY),&fileinfo); + fclose(*TALLY); + } + + if ( lstat_ret ) { + _pam_log(LOG_ALERT, "Couldn't stat %s",filename); + return PAM_AUTH_ERR; + } + + if((fileinfo.st_mode & S_IWOTH) || !S_ISREG(fileinfo.st_mode)) { + /* If the file is world writable or is not a + normal file, return error */ + _pam_log(LOG_ALERT, + "%s is either world writable or not a normal file", + filename); + return PAM_AUTH_ERR; + } + + if ( ! ( *TALLY = fopen(filename,(*tally!=TALLY_HI)?"r+":"r") ) ) { + _pam_log(LOG_ALERT, "Error opening %s for update", filename); + +/* Discovering why account service fails: e/uid are target user. + * + * perror(MODULE_NAME); + * fprintf(stderr,"uid %d euid %d\n",getuid(), geteuid()); + */ + return PAM_AUTH_ERR; + } + + if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) { + _pam_log(LOG_ALERT, "fseek failed %s", filename); + return PAM_AUTH_ERR; + } + + + if ( ( fread((char *) &faillog, sizeof faillog, 1, *TALLY) )==0 ) { + *tally=0; /* Assuming a gappy filesystem */ + } + *tally = faillog.fail_cnt; + + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- Support function: update and close tallyfile with tally!=TALLY_HI --- */ + +static int set_tally( tally_t tally, + uid_t uid, + const char *filename, + FILE **TALLY ) + { + if ( tally!=TALLY_HI ) + { + if ( fseek( *TALLY, uid * sizeof faillog, SEEK_SET ) ) { + _pam_log(LOG_ALERT, "fseek failed %s", filename); + return PAM_AUTH_ERR; + } + faillog.fail_cnt = tally; + if ( fwrite((char *) &faillog, sizeof faillog, 1, *TALLY)==0 ) { + _pam_log(LOG_ALERT, "tally update (fputc) failed.", filename); + return PAM_AUTH_ERR; + } + } + + if ( fclose(*TALLY) ) { + _pam_log(LOG_ALERT, "tally update (fclose) failed.", filename); + return PAM_AUTH_ERR; + } + *TALLY=NULL; + return PAM_SUCCESS; + } + +/*---------------------------------------------------------------------*/ + +/* --- PAM bits --- */ + +#ifndef MAIN + +#define PAM_FUNCTION(name) \ + PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv) + +#define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS)) + +/*---------------------------------------------------------------------*/ + +/* --- tally bump function: bump tally for uid by (signed) inc --- */ + +static int tally_bump (int inc, + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) { + uid_t uid; + + int + fail_on_error = FALSE; + tally_t + tally = 0; /* !TALLY_HI --> Log opened for update */ + + char + no_magic_root = FALSE; + + char + filename[ FILENAME_MAX ] = DEFAULT_LOGFILE; + + /* Should probably decode the parameters before anything else. */ + + { + for ( ; argc-- > 0; ++argv ) { + + /* generic options.. um, ignored. :] */ + + if ( ! strcmp( *argv, "no_magic_root" ) ) { + no_magic_root = TRUE; + } + else if ( ! strncmp( *argv, "file=", 5 ) ) { + char const + *from = (*argv)+5; + char + *to = filename; + if ( *from!='/' || strlen(from)>FILENAME_MAX-1 ) { + _pam_log(LOG_ERR, + MODULE_NAME ": filename not /rooted or too long; ", + *argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + while ( ( *to++ = *from++ ) ); + } + else if ( ! strcmp( *argv, "onerr=fail" ) ) { + fail_on_error=TRUE; + } + else if ( ! strcmp( *argv, "onerr=succeed" ) ) { + fail_on_error=FALSE; + } + else { + _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv); + } + } /* for() */ + } + + { + FILE + *TALLY = NULL; + const char + *user = NULL, + *remote_host = NULL; + + int i=pam_get_uid(pamh, &uid, &user); + if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); + + i=get_tally( &tally, uid, filename, &TALLY ); + fail_time = faillog.fail_time; /* to remember old fail time (for locktime) */ + faillog.fail_time = (time_t) time( (time_t *) 0); + (void) pam_get_item(pamh, PAM_RHOST, (const void **)&remote_host); + if (!remote_host) + { + strcpy(faillog.fail_line, "unknown"); + } + else + { + strncpy(faillog.fail_line, remote_host, (size_t)sizeof(faillog.fail_line)); + faillog.fail_line[sizeof(faillog.fail_line)-1] = 0; + } + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + + if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + + tally+=inc; + + if ( tally==TALLY_HI ) { /* Overflow *and* underflow. :) */ + tally-=inc; + _pam_log(LOG_ALERT,"Tally %sflowed for user %s", + (inc<0)?"under":"over",user); + } + } + + i=set_tally( tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + } + + return PAM_SUCCESS; +} + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_AUTH + +PAM_FUNCTION( pam_sm_authenticate ) { + return tally_bump( 1, pamh, flags, argc, argv); +} + +/* --- Seems to need this function. Ho hum. --- */ + +PAM_FUNCTION( pam_sm_setcred ) { return PAM_SUCCESS; } + +#endif + +/*---------------------------------------------------------------------*/ + +/* --- session management functions (only) --- */ + +/* + * Unavailable until .so files can be suid + */ + +#ifdef PAM_SM_SESSION + +/* To maintain a balance-tally of successful login/outs */ + +PAM_FUNCTION( pam_sm_open_session ) { + return tally_bump( 1, pamh, flags, argc, argv); +} + +PAM_FUNCTION( pam_sm_close_session ) { + return tally_bump(-1, pamh, flags, argc, argv); +} + +#endif + +/*---------------------------------------------------------------------*/ + +/* --- authentication management functions (only) --- */ + +#ifdef PAM_SM_AUTH + +/* To lock out a user with an unacceptably high tally */ + +PAM_FUNCTION( pam_sm_acct_mgmt ) { + uid_t + uid; + + int + fail_on_error = FALSE; + tally_t + deny = 0; + tally_t + tally = 0; /* !TALLY_HI --> Log opened for update */ + + char + no_magic_root = FALSE, + even_deny_root_account = FALSE; + char per_user = FALSE; /* if true then deny=.fail_max for user */ + char no_lock_time = FALSE; /* if true then don't use .fail_locktime */ + + const char + *user = NULL; + + enum TALLY_RESET + reset = TALLY_RESET_DEFAULT; + + char + filename[ FILENAME_MAX ] = DEFAULT_LOGFILE; + + /* Should probably decode the parameters before anything else. */ + + { + for ( ; argc-- > 0; ++argv ) { + + /* generic options.. um, ignored. :] */ + + if ( ! strcmp( *argv, "no_magic_root" ) ) { + no_magic_root = TRUE; + } + else if ( ! strcmp( *argv, "even_deny_root_account" ) ) { + even_deny_root_account = TRUE; + } + else if ( ! strcmp( *argv, "reset" ) ) { + reset = TALLY_RESET_RESET; + } + else if ( ! strcmp( *argv, "no_reset" ) ) { + reset = TALLY_RESET_NO_RESET; + } + else if ( ! strncmp( *argv, "file=", 5 ) ) { + char const + *from = (*argv)+5; + char + *to = filename; + if ( *from != '/' || strlen(from) > FILENAME_MAX-1 ) { + _pam_log(LOG_ERR, + MODULE_NAME ": filename not /rooted or too long; ", + *argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + while ( ( *to++ = *from++ ) ); + } + else if ( ! strncmp( *argv, "deny=", 5 ) ) { + if ( sscanf((*argv)+5,TALLY_FMT,&deny) != 1 ) { + _pam_log(LOG_ERR,"bad number supplied; %s",*argv); + RETURN_ERROR( PAM_AUTH_ERR ); + } + } + else if ( ! strcmp( *argv, "onerr=fail" ) ) { + fail_on_error=TRUE; + } + else if ( ! strcmp( *argv, "onerr=succeed" ) ) { + fail_on_error=FALSE; + } + else if ( ! strcmp( *argv, "per_user" ) ) + { + per_user = TRUE; + } + else if ( ! strcmp( *argv, "no_lock_time") ) + { + no_lock_time = TRUE; + } + else { + _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv); + } + } /* for() */ + } + + { + FILE *TALLY=0; + int i=pam_get_uid(pamh, &uid, &user); + if ( i != PAM_SUCCESS ) RETURN_ERROR( i ); + + i=get_tally( &tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + + if ( no_magic_root || getuid() ) { /* no_magic_root kills uid test */ + + /* To deny or not to deny; that is the question */ + + /* if there's .fail_max entry and per_user=TRUE then deny=.fail_max */ + + if ( (faillog.fail_max) && (per_user) ) {deny = faillog.fail_max;} + if (faillog.fail_locktime && fail_time && (!no_lock_time) ) + { + if ( (faillog.fail_locktime + fail_time) > (time_t)time((time_t)0) ) + { + _pam_log(LOG_NOTICE,"user %s ("UID_FMT") has time limit [%lds left] since last failure.",user,uid,fail_time+faillog.fail_locktime-(time_t)time((time_t)0)); + return PAM_AUTH_ERR; + } + } + if ( + ( deny != 0 ) && /* deny==0 means no deny */ + ( tally > deny ) && /* tally>deny means exceeded */ + ( even_deny_root_account || uid ) /* even_deny stops uid check */ + ) { + _pam_log(LOG_NOTICE,"user %s ("UID_FMT") tally "TALLY_FMT", deny "TALLY_FMT, + user, uid, tally, deny); + return PAM_AUTH_ERR; /* Only unconditional failure */ + } + + /* resets for explicit reset + * or by default if deny exists and not magic-root + */ + + if ( ( reset == TALLY_RESET_RESET ) || + ( reset == TALLY_RESET_DEFAULT && deny ) ) { tally=0; } + } + else /* is magic root */ { + + /* Magic root skips deny test... */ + + /* Magic root only resets on explicit reset, regardless of deny */ + + if ( reset == TALLY_RESET_RESET ) { tally=0; } + } + if (tally == 0) + { + faillog.fail_time = (time_t) 0; + strcpy(faillog.fail_line, ""); + } + i=set_tally( tally, uid, filename, &TALLY ); + if ( i != PAM_SUCCESS ) { if (TALLY) fclose(TALLY); RETURN_ERROR( i ); } + } + + return PAM_SUCCESS; +} + +#endif /* #ifdef PAM_SM_AUTH */ + +/*-----------------------------------------------------------------------*/ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_tally_modstruct = { + MODULE_NAME, +#ifdef PAM_SM_AUTH + pam_sm_authenticate, + pam_sm_setcred, +#else + NULL, + NULL, +#endif +#ifdef PAM_SM_ACCOUNT + pam_sm_acct_mgmt, +#else + NULL, +#endif +#ifdef PAM_SM_SESSION + pam_sm_open_session, + pam_sm_close_session, +#else + NULL, + NULL, +#endif +#ifdef PAM_SM_PASSWORD + pam_sm_chauthtok, +#else + NULL, +#endif +}; + +#endif /* #ifdef PAM_STATIC */ + +/*-----------------------------------------------------------------------*/ + +#else /* #ifndef MAIN */ + +static const char *cline_filename = DEFAULT_LOGFILE; +static tally_t cline_reset = TALLY_HI; /* Default is `interrogate only' */ +static int cline_quiet = 0; + +/* + * Not going to link with pamlib just for these.. :) + */ + +static const char * pam_errors( int i ) { + switch (i) { + case PAM_AUTH_ERR: return "Authentication error"; + case PAM_SERVICE_ERR: return "Service error"; + case PAM_USER_UNKNOWN: return "Unknown user"; + default: return "Unknown error"; + } +} + +static int getopts( int argc, char **argv ) { + const char *pname = *argv; + for ( ; *argv ; (void)(*argv && ++argv) ) { + if ( !strcmp (*argv,"--file") ) cline_filename=*++argv; + else if ( !strncmp(*argv,"--file=",7) ) cline_filename=*argv+7; + else if ( !strcmp (*argv,"--user") ) cline_user=*++argv; + else if ( !strncmp(*argv,"--user=",7) ) cline_user=*argv+7; + else if ( !strcmp (*argv,"--reset") ) cline_reset=0; + else if ( !strncmp(*argv,"--reset=",8)) { + if ( sscanf(*argv+8,TALLY_FMT,&cline_reset) != 1 ) + fprintf(stderr,"%s: Bad number given to --reset=\n",pname), exit(0); + } + else if ( !strcmp (*argv,"--quiet") ) cline_quiet=1; + else { + fprintf(stderr,"%s: Unrecognised option %s\n",pname,*argv); + return FALSE; + } + } + return TRUE; +} + +int main ( int argc, char **argv ) { + + if ( ! getopts( argc, argv+1 ) ) { + printf("%s: [--file rooted-filename] [--user username] " + "[--reset[=n]] [--quiet]\n", + *argv); + exit(0); + } + + umask(077); + + /* + * Major difference between individual user and all users: + * --user just handles one user, just like PAM. + * --user=* handles all users, sniffing cline_filename for nonzeros + */ + + if ( cline_user ) { + uid_t uid; + tally_t tally=cline_reset; + FILE *TALLY=0; + int i=pam_get_uid( NULL, &uid, NULL); + if ( i != PAM_SUCCESS ) { + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + + i=get_tally( &tally, uid, cline_filename, &TALLY ); + if ( i != PAM_SUCCESS ) { + if (TALLY) fclose(TALLY); + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + + if ( !cline_quiet ) + printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",cline_user,uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + + i=set_tally( cline_reset, uid, cline_filename, &TALLY ); + if ( i != PAM_SUCCESS ) { + if (TALLY) fclose(TALLY); + fprintf(stderr,"%s: %s\n",*argv,pam_errors(i)); + exit(0); + } + } + else /* !cline_user (ie, operate on all users) */ { + FILE *TALLY=fopen(cline_filename, "r"); + uid_t uid=0; + if ( !TALLY ) perror(*argv), exit(0); + + for ( ; !feof(TALLY); uid++ ) { + tally_t tally; + struct passwd *pw; + if ( ! fread((char *) &faillog, sizeof faillog, 1, TALLY) || ! faillog.fail_cnt ) { + tally=faillog.fail_cnt; + continue; + } + tally = faillog.fail_cnt; + + if ( ( pw=getpwuid(uid) ) ) { + printf("User %s\t("UID_FMT")\t%s "TALLY_FMT"\n",pw->pw_name,uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + } + else { + printf("User [NONAME]\t("UID_FMT")\t%s "TALLY_FMT"\n",uid, + (cline_reset!=TALLY_HI)?"had":"has",tally); + } + } + fclose(TALLY); + if ( cline_reset!=0 && cline_reset!=TALLY_HI ) { + fprintf(stderr,"%s: Can't reset all users to non-zero\n",*argv); + } + else if ( !cline_reset ) { + TALLY=fopen(cline_filename, "w"); + if ( !TALLY ) perror(*argv), exit(0); + fclose(TALLY); + } + } + return 0; +} + + +#endif diff --git a/modules/pam_tally/pam_tally_app.c b/modules/pam_tally/pam_tally_app.c new file mode 100644 index 00000000..9e6e1faf --- /dev/null +++ b/modules/pam_tally/pam_tally_app.c @@ -0,0 +1,7 @@ +/* + # This seemed like such a good idea at the time. :) + */ + +#define MAIN +#include "pam_tally.c" + diff --git a/modules/pam_time/.cvsignore b/modules/pam_time/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_time/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_time/Makefile b/modules/pam_time/Makefile new file mode 100644 index 00000000..03105bb1 --- /dev/null +++ b/modules/pam_time/Makefile @@ -0,0 +1,98 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Andrew Morgan 1996/6/11 +# + +TITLE=pam_time +CONFD=$(CONFIGED)/security +export CONFD +CONFILE=$(CONFD)/time.conf +export CONFILE + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +DEFS=-DCONFILE=\"$(CONFILE)\" + +CFLAGS += $(DEFS) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all +ifdef DYNAMIC + $(MKDIR) $(FAKEROOT)$(SECUREDIR) + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MKDIR) $(FAKEROOT)$(SCONFIGED) + bash -f ./install_conf + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + rm -f $(FAKEROOT)$(CONFILE) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + rm -f ./.ignore_age + +extraclean: clean + rm -f *.a *.o *.so *.bak + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_time/README b/modules/pam_time/README new file mode 100644 index 00000000..dcbe35e4 --- /dev/null +++ b/modules/pam_time/README @@ -0,0 +1,43 @@ +$Id$ + +This is a help file for the pam_time module. It explains the need for +pam_time and also the syntax of the /etc/security/time.conf file. +[a lot of the syntax is freely adapted from the porttime file of the +shadow suite.] + +1. Introduction +=============== + +It is desirable to restrict access to a system and or specific +applications at various times of the day and on specific days or over +various terminal lines. + +The pam_time module is intended to offer a configurable module that +satisfies this purpose, within the context of Linux-PAM. + +2. the /etc/security/time.conf file +=================================== + +This file is the configuration script for defining time/port access +control to the system/applications. + +Its syntax is described in the sample ./time.conf provided in this +directory. + +unrecognised rules are ignored (but an error is logged to syslog(3)) + +-------------------- +Bugs to Andrew or the list + +######################################################################## +# $Log$ +# Revision 1.1 2000/06/20 22:12:00 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:16 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.3 1997/01/04 20:42:43 morgan +# I want email on parc now +# +# \ No newline at end of file diff --git a/modules/pam_time/install_conf b/modules/pam_time/install_conf new file mode 100755 index 00000000..051d8b70 --- /dev/null +++ b/modules/pam_time/install_conf @@ -0,0 +1,46 @@ +#!/bin/bash + +CONFILE=$FAKEROOT"$CONFILE" +IGNORE_AGE=./.ignore_age +QUIET_INSTALL=../../.quiet_install +CONF=./time.conf +MODULE=pam_time + +echo + +if [ -f "$QUIET_INSTALL" ]; then + if [ ! -f "$CONFILE" ]; then + yes="y" + else + yes="skip" + fi +elif [ -f "$IGNORE_AGE" ]; then + echo "you don't want to be bothered with the age of your $CONFILE file" + yes="n" +elif [ ! -f "$CONFILE" ] || [ "$CONF" -nt "$CONFILE" ]; then + if [ -f "$CONFILE" ]; then + echo "An older $MODULE configuration file already exists ($CONFILE)" + echo "Do you wish to copy the $CONF file in this distribution" + echo "to $CONFILE ? (y/n) [skip] " + read yes + else + yes="y" + fi +else + yes="skip" +fi + +if [ "$yes" = "y" ]; then + mkdir -p $FAKEROOT$CONFD + echo " copying $CONF to $CONFILE" + cp $CONF $CONFILE +else + echo " Skipping $CONF installation" + if [ "$yes" = "n" ]; then + touch "$IGNORE_AGE" + fi +fi + +echo + +exit 0 diff --git a/modules/pam_time/pam_time.c b/modules/pam_time/pam_time.c new file mode 100644 index 00000000..2ca51859 --- /dev/null +++ b/modules/pam_time/pam_time.c @@ -0,0 +1,612 @@ +/* pam_time module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/6/22 + * (File syntax and much other inspiration from the shadow package + * shadow-960129) + */ + +const static char rcsid[] = +"$Id$;\n" +"\t\tVersion 0.22 for Linux-PAM\n" +"Copyright (C) Andrew G. Morgan 1996 \n"; + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAM_TIME_CONF CONFILE /* from external define */ +#define PAM_TIME_BUFLEN 1000 +#define FIELD_SEPARATOR ';' /* this is new as of .02 */ + +typedef enum { FALSE, TRUE } boolean; +typedef enum { AND, OR } operator; + +/* + * here, we make definitions for the externally accessible functions + * in this file (these definitions are required for static modules + * but strongly encouraged generally) they are used to instruct the + * modules include file to define their prototypes. + */ + +#define PAM_SM_ACCOUNT + +#include +#include + +/* --- static functions for checking whether the user should be let in --- */ + +static void _log_err(const char *format, ... ) +{ + va_list args; + + va_start(args, format); + openlog("pam_time", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(LOG_CRIT, format, args); + va_end(args); + closelog(); +} + +static void shift_bytes(char *mem, int from, int by) +{ + while (by-- > 0) { + *mem = mem[from]; + ++mem; + } +} + +static int read_field(int fd, char **buf, int *from, int *to) +{ + /* is buf set ? */ + + if (! *buf) { + *buf = (char *) malloc(PAM_TIME_BUFLEN); + if (! *buf) { + _log_err("out of memory"); + D(("no memory")); + return -1; + } + *from = *to = 0; + fd = open(PAM_TIME_CONF, O_RDONLY); + } + + /* do we have a file open ? return error */ + + if (fd < 0 && *to <= 0) { + _log_err( PAM_TIME_CONF " not opened"); + memset(*buf, 0, PAM_TIME_BUFLEN); + _pam_drop(*buf); + return -1; + } + + /* check if there was a newline last time */ + + if ((*to > *from) && (*to > 0) + && ((*buf)[*from] == '\0')) { /* previous line ended */ + (*from)++; + (*buf)[0] = '\0'; + return fd; + } + + /* ready for more data: first shift the buffer's remaining data */ + + *to -= *from; + shift_bytes(*buf, *from, *to); + *from = 0; + (*buf)[*to] = '\0'; + + while (fd >= 0 && *to < PAM_TIME_BUFLEN) { + int i; + + /* now try to fill the remainder of the buffer */ + + i = read(fd, *to + *buf, PAM_TIME_BUFLEN - *to); + if (i < 0) { + _log_err("error reading " PAM_TIME_CONF); + return -1; + } else if (!i) { + close(fd); + fd = -1; /* end of file reached */ + } else + *to += i; + + /* + * contract the buffer. Delete any comments, and replace all + * multiple spaces with single commas + */ + + i = 0; +#ifdef DEBUG_DUMP + D(("buffer=<%s>",*buf)); +#endif + while (i < *to) { + if ((*buf)[i] == ',') { + int j; + + for (j=++i; j<*to && (*buf)[j] == ','; ++j); + if (j!=i) { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + } + } + switch ((*buf)[i]) { + int j,c; + case '#': + for (j=i; j < *to && (c = (*buf)[j]) != '\n'; ++j); + if (j >= *to) { + (*buf)[*to = ++i] = '\0'; + } else if (c == '\n') { + shift_bytes(i + (*buf), j-i, (*to) - j); + *to -= j-i; + ++i; + } else { + _log_err("internal error in " __FILE__ + " at line %d", __LINE__ ); + return -1; + } + break; + case '\\': + if ((*buf)[i+1] == '\n') { + shift_bytes(i + *buf, 2, *to - (i+2)); + *to -= 2; + } + break; + case '!': + case ' ': + case '\t': + if ((*buf)[i] != '!') + (*buf)[i] = ','; + /* delete any trailing spaces */ + for (j=++i; j < *to && ( (c = (*buf)[j]) == ' ' + || c == '\t' ); ++j); + shift_bytes(i + *buf, j-i, (*to)-j ); + *to -= j-i; + break; + default: + ++i; + } + } + } + + (*buf)[*to] = '\0'; + + /* now return the next field (set the from/to markers) */ + { + int i; + + for (i=0; i<*to; ++i) { + switch ((*buf)[i]) { + case '#': + case '\n': /* end of the line/file */ + (*buf)[i] = '\0'; + *from = i; + return fd; + case FIELD_SEPARATOR: /* end of the field */ + (*buf)[i] = '\0'; + *from = ++i; + return fd; + } + } + *from = i; + (*buf)[*from] = '\0'; + } + + if (*to <= 0) { + D(("[end of text]")); + *buf = NULL; + } + + return fd; +} + +/* read a member from a field */ + +static int logic_member(const char *string, int *at) +{ + int len,c,to; + int done=0; + int token=0; + + len=0; + to=*at; + do { + c = string[to++]; + + switch (c) { + + case '\0': + --to; + done = 1; + break; + + case '&': + case '|': + case '!': + if (token) { + --to; + } + done = 1; + break; + + default: + if (isalpha(c) || c == '*' || isdigit(c) || c == '_' + || c == '-' || c == '.') { + token = 1; + } else if (token) { + --to; + done = 1; + } else { + ++*at; + } + } + } while (!done); + + return to - *at; +} + +typedef enum { VAL, OP } expect; + +static boolean logic_field(const void *me, const char *x, int rule, + boolean (*agrees)(const void *, const char * + , int, int)) +{ + boolean left=FALSE, right, not=FALSE; + operator oper=OR; + int at=0, l; + expect next=VAL; + + while ((l = logic_member(x,&at))) { + int c = x[at]; + + if (next == VAL) { + if (c == '!') + not = !not; + else if (isalpha(c) || c == '*') { + right = not ^ agrees(me, x+at, l, rule); + if (oper == AND) + left &= right; + else + left |= right; + next = OP; + } else { + _log_err("garbled syntax; expected name (rule #%d)", rule); + return FALSE; + } + } else { /* OP */ + switch (c) { + case '&': + oper = AND; + break; + case '|': + oper = OR; + break; + default: + _log_err("garbled syntax; expected & or | (rule #%d)" + , rule); + D(("%c at %d",c,at)); + return FALSE; + } + next = VAL; + } + at += l; + } + + return left; +} + +static boolean is_same(const void *A, const char *b, int len, int rule) +{ + int i; + const char *a; + + a = A; + for (i=0; len > 0; ++i, --len) { + if (b[i] != a[i]) { + if (b[i++] == '*') { + return (!--len || !strncmp(b+i,a+strlen(a)-len,len)); + } else + return FALSE; + } + } + return ( !len ); +} + +typedef struct { + int day; /* array of 7 bits, one set for today */ + int minute; /* integer, hour*100+minute for now */ +} TIME; + +struct day { + const char *d; + int bit; +} static const days[11] = { + { "su", 01 }, + { "mo", 02 }, + { "tu", 04 }, + { "we", 010 }, + { "th", 020 }, + { "fr", 040 }, + { "sa", 0100 }, + { "wk", 076 }, + { "wd", 0101 }, + { "al", 0177 }, + { NULL, 0 } +}; + +static TIME time_now(void) +{ + struct tm *local; + time_t the_time; + TIME this; + + the_time = time((time_t *)0); /* get the current time */ + local = localtime(&the_time); + this.day = days[local->tm_wday].bit; + this.minute = local->tm_hour*100 + local->tm_min; + + D(("day: 0%o, time: %.4d", this.day, this.minute)); + return this; +} + +/* take the current date and see if the range "date" passes it */ +static boolean check_time(const void *AT, const char *times, int len, int rule) +{ + boolean not,pass; + int marked_day, time_start, time_end; + const TIME *at; + int i,j=0; + + at = AT; + D(("chcking: 0%o/%.4d vs. %s", at->day, at->minute, times)); + + if (times == NULL) { + /* this should not happen */ + _log_err("internal error: " __FILE__ " line %d", __LINE__); + return FALSE; + } + + if (times[j] == '!') { + ++j; + not = TRUE; + } else { + not = FALSE; + } + + for (marked_day = 0; len > 0 && isalpha(times[j]); --len) { + int this_day=-1; + + D(("%c%c ?", times[j], times[j+1])); + for (i=0; days[i].d != NULL; ++i) { + if (tolower(times[j]) == days[i].d[0] + && tolower(times[j+1]) == days[i].d[1] ) { + this_day = days[i].bit; + break; + } + } + j += 2; + if (this_day == -1) { + _log_err("bad day specified (rule #%d)", rule); + return FALSE; + } + marked_day ^= this_day; + } + if (marked_day == 0) { + _log_err("no day specified"); + return FALSE; + } + D(("day range = 0%o", marked_day)); + + time_start = 0; + for (i=0; len > 0 && i < 4 && isdigit(times[i+j]); ++i, --len) { + time_start *= 10; + time_start += times[i+j]-'0'; /* is this portable? */ + } + j += i; + + if (times[j] == '-') { + time_end = 0; + for (i=1; len > 0 && i < 5 && isdigit(times[i+j]); ++i, --len) { + time_end *= 10; + time_end += times[i+j]-'0'; /* is this portable */ + } + j += i; + } else + time_end = -1; + + D(("i=%d, time_end=%d, times[j]='%c'", i, time_end, times[j])); + if (i != 5 || time_end == -1) { + _log_err("no/bad times specified (rule #%d)", rule); + return TRUE; + } + D(("times(%d to %d)", time_start,time_end)); + D(("marked_day = 0%o", marked_day)); + + /* compare with the actual time now */ + + pass = FALSE; + if (time_start < time_end) { /* start < end ? --> same day */ + if ((at->day & marked_day) && (at->minute >= time_start) + && (at->minute < time_end)) { + D(("time is listed")); + pass = TRUE; + } + } else { /* spans two days */ + if ((at->day & marked_day) && (at->minute >= time_start)) { + D(("caught on first day")); + pass = TRUE; + } else { + marked_day <<= 1; + marked_day |= (marked_day & 0200) ? 1:0; + D(("next day = 0%o", marked_day)); + if ((at->day & marked_day) && (at->minute <= time_end)) { + D(("caught on second day")); + pass = TRUE; + } + } + } + + return (not ^ pass); +} + +static int check_account(const char *service + , const char *tty, const char *user) +{ + int from=0,to=0,fd=-1; + char *buffer=NULL; + int count=0; + TIME here_and_now; + int retval=PAM_SUCCESS; + + here_and_now = time_now(); /* find current time */ + do { + boolean good=TRUE,intime; + + /* here we get the service name field */ + + fd = read_field(fd,&buffer,&from,&to); + + if (!buffer || !buffer[0]) { + /* empty line .. ? */ + continue; + } + ++count; + + good = logic_field(service, buffer, count, is_same); + D(("with service: %s", good ? "passes":"fails" )); + + /* here we get the terminal name field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_TIME_CONF "; no tty entry #%d", count); + continue; + } + good &= logic_field(tty, buffer, count, is_same); + D(("with tty: %s", good ? "passes":"fails" )); + + /* here we get the username field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_TIME_CONF "; no user entry #%d", count); + continue; + } + good &= logic_field(user, buffer, count, is_same); + D(("with user: %s", good ? "passes":"fails" )); + + /* here we get the time field */ + + fd = read_field(fd,&buffer,&from,&to); + if (!buffer || !buffer[0]) { + _log_err(PAM_TIME_CONF "; no time entry #%d", count); + continue; + } + + intime = logic_field(&here_and_now, buffer, count, check_time); + D(("with time: %s", intime ? "passes":"fails" )); + + fd = read_field(fd,&buffer,&from,&to); + if (buffer && buffer[0]) { + _log_err(PAM_TIME_CONF "; poorly terminated rule #%d", count); + continue; + } + + if (good && !intime) { + /* + * for security parse whole file.. also need to ensure + * that the buffer is free()'d and the file is closed. + */ + retval = PAM_PERM_DENIED; + } else { + D(("rule passed")); + } + } while (buffer); + + return retval; +} + +/* --- public account management functions --- */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + const char *service=NULL, *tty=NULL; + const char *user=NULL; + + /* set service name */ + + if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) + != PAM_SUCCESS || service == NULL) { + _log_err("cannot find the current service name"); + return PAM_ABORT; + } + + /* set username */ + + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL + || *user == '\0') { + _log_err("cannot determine the user's name"); + return PAM_USER_UNKNOWN; + } + + /* set tty name */ + + if (pam_get_item(pamh, PAM_TTY, (const void **)&tty) != PAM_SUCCESS + || tty == NULL) { + D(("PAM_TTY not set, probing stdin")); + tty = ttyname(STDIN_FILENO); + if (tty == NULL) { + _log_err("couldn't get the tty name"); + return PAM_ABORT; + } + if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) { + _log_err("couldn't set tty name"); + return PAM_ABORT; + } + } + + if (strncmp("/dev/",tty,5) == 0) { /* strip leading /dev/ */ + tty += 5; + } + + /* good, now we have the service name, the user and the terminal name */ + + D(("service=%s", service)); + D(("user=%s", user)); + D(("tty=%s", tty)); + + return check_account(service,tty,user); +} + +/* end of module definition */ + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_time_modstruct = { + "pam_time", + NULL, + NULL, + pam_sm_acct_mgmt, + NULL, + NULL, + NULL +}; +#endif diff --git a/modules/pam_time/time.conf b/modules/pam_time/time.conf new file mode 100644 index 00000000..d2062fdb --- /dev/null +++ b/modules/pam_time/time.conf @@ -0,0 +1,64 @@ +# this is an example configuration file for the pam_time module. Its syntax +# was initially based heavily on that of the shadow package (shadow-960129). +# +# the syntax of the lines is as follows: +# +# services;ttys;users;times +# +# white space is ignored and lines maybe extended with '\\n' (escaped +# newlines). As should be clear from reading these comments, +# text following a '#' is ignored to the end of the line. +# +# the combination of individual users/terminals etc is a logic list +# namely individual tokens that are optionally prefixed with '!' (logical +# not) and separated with '&' (logical and) and '|' (logical or). +# +# services +# is a logic list of PAM service names that the rule applies to. +# +# ttys +# is a logic list of terminal names that this rule applies to. +# +# users +# is a logic list of users to whom this rule applies. +# +# NB. For these items the simple wildcard '*' may be used only once. +# +# times +# the format here is a logic list of day/time-range +# entries the days are specified by a sequence of two character +# entries, MoTuSa for example is Monday Tuesday and Saturday. Note +# that repeated days are unset MoMo = no day, and MoWk = all weekdays +# bar Monday. The two character combinations accepted are +# +# Mo Tu We Th Fr Sa Su Wk Wd Al +# +# the last two being week-end days and all 7 days of the week +# respectively. As a final example, AlFr means all days except Friday. +# +# each day/time-range can be prefixed with a '!' to indicate "anything +# but" +# +# The time-range part is two 24-hour times HHMM separated by a hyphen +# indicating the start and finish time (if the finish time is smaller +# than the start time it is deemed to apply on the following day). +# +# for a rule to be active, ALL of service+ttys+users must be satisfied +# by the applying process. +# + +# +# Here is a simple example: running blank on tty* (any ttyXXX device), +# the users 'you' and 'me' are denied service all of the time +# + +#blank;tty* & !ttyp*;you|me;!Al0000-2400 + +# Another silly example, user 'root' is denied xsh access +# from pseudo terminals at the weekend and on mondays. + +#xsh;ttyp*;root;!WdMo0000-2400 + +# +# End of example file. +# \ No newline at end of file diff --git a/modules/pam_unix/.cvsignore b/modules/pam_unix/.cvsignore new file mode 100644 index 00000000..64c5ce5c --- /dev/null +++ b/modules/pam_unix/.cvsignore @@ -0,0 +1,4 @@ +dynamic +unix_chkpwd +*.so +*~ diff --git a/modules/pam_unix/CHANGELOG b/modules/pam_unix/CHANGELOG new file mode 100644 index 00000000..1476b579 --- /dev/null +++ b/modules/pam_unix/CHANGELOG @@ -0,0 +1,55 @@ +$Id$ + +* Mon Aug 16 1999 Jan Rêkorajski +- fixed reentrancy problems + +* Sun Jul 4 21:03:42 PDT 1999 + +- temporarily removed the crypt16 stuff. I'm really paranoid about + crypto stuff and exporting it, and there are a few too many 's-box' + references in the code for my liking.. + +* Wed Jun 30 1999 Steve Langasek +- further NIS+ fixes + +* Sun Jun 27 1999 Steve Langasek +- fix to uid-handling code for NIS+ + +* Sat Jun 26 1999 Jan Rêkorajski +- merged MD5 fix and early failure syslog + by Andrey Vladimirovich Savochkin +- minor fixes +- added signal handler to unix_chkpwd + +* Fri Jun 25 1999 Stephen Langasek +- reorganized the code to let it build as separate C files + +* Sun Jun 20 1999 Jan Rêkorajski +- fixes in pam_unix_auth, it incorrectly saved and restored return + value when likeauth option was used + +* Tue Jun 15 1999 Jan Rêkorajski +- added NIS+ support + +* Mon Jun 14 1999 Jan Rêkorajski +- total rewrite based on pam_pwdb module, now there is ONE pam_unix.so + module, it accepts the same options as pam_pwdb - all of them correctly ;) + (pam_pwdb dosn't understand what DISALLOW_NULL_AUTHTOK means) + +* Tue Apr 20 1999 Jan Rêkorajski +- Arghhh, pam_unix_passwd was not updating /etc/shadow when used with + pam_cracklib. + +* Mon Apr 19 1999 Jan Rêkorajski +- added "remember=XXX" option that means 'remember XXX old passwords' + Old passwords are stored in /etc/security/opasswd, there can be + maximum of 400 passwords per user. + +* Sat Mar 27 1999 Jan Rêkorajski +- added crypt16 to pam_unix_auth and pam_unix_passwd (check only, this algorithm + is too lame to use it in real life) + +* Sun Mar 21 1999 Jan Rêkorajski +- pam_unix_auth now correctly behave when user has NULL AUTHTOK +- pam_unix_auth returns PAM_PERM_DENIED when seteuid fails + diff --git a/modules/pam_unix/Makefile b/modules/pam_unix/Makefile new file mode 100644 index 00000000..89b33cfd --- /dev/null +++ b/modules/pam_unix/Makefile @@ -0,0 +1,155 @@ +# $Id$ +# +# This Makefile controls a build process of the pam_unix modules +# for Linux-PAM. You should not modify this Makefile. +# + +######################################################################## +# some options... uncomment to take effect +######################################################################## + +# do you want cracklib? +ifeq ($(HAVE_CRACKLIB),yes) +USE_CRACKLIB=-D"USE_CRACKLIB" +endif + +# do you want to use lckpwdf? +USE_LCKPWDF=-D"USE_LCKPWDF" + +# do you need to include the locking functions in the source? +#NEED_LCKPWDF=-D"NEED_LCKPWDF" + +ifeq ($(shell ./need_nsl.sh),yes) +LIBNSL = -lnsl +endif + +CHKPWD=unix_chkpwd + +EXTRAS += -DCHKPWD_HELPER=\"$(SUPLEMENTED)/$(CHKPWD)\" + +######################################################################## + +CFLAGS += $(USE_CRACKLIB) $(USE_LCKPWDF) $(NEED_LCKPWDF) $(EXTRAS) +LDLIBS = $(EXTRALS) + +ifdef USE_CRACKLIB +CRACKLIB = -lcrack +endif + + +LIBOBJ = pam_unix_auth.o pam_unix_acct.o pam_unix_sess.o pam_unix_passwd.o \ + support.o +LIBSRC = pam_unix_auth.c pam_unix_acct.c pam_unix_sess.c pam_unix_passwd.c \ + support.c +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +PLUS = md5_good.o md5_broken.o md5_crypt_good.o md5_crypt_broken.o \ + yppasswd_xdr.o bigcrypt.o + +ifdef DYNAMIC +LIBSHARED = pam_unix.so +endif +ifdef STATIC +LIBSTATIC = libpam_unix.o +endif + + +########################### don't edit below ####################### + +all: dirs info $(PLUS) $(LIBSHARED) $(LIBSTATIC) $(CHKPWD) register + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o: %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +info: + @echo + @echo "*** Building pam-unix module of the framework..." + @echo + +dirs: +ifdef DYNAMIC + mkdir -p ./dynamic +endif +ifdef STATIC + mkdir -p ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static pam_unix_auth pam_unix/$(LIBSTATIC) ; \ + ./register_static pam_unix_acct "" ; \ + ./register_static pam_unix_session "" ; \ + ./register_static pam_unix_passwd "" ; \ + ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(PLUS) $(CRACKLIB) $(LDLIBS) $(LIBNSL) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(PLUS) $(CRACKLIB) $(LDLIBS) $(LIBNSL) +endif + +$(CHKPWD): unix_chkpwd.o md5_good.o md5_broken.o \ + md5_crypt_good.o md5_crypt_broken.o \ + bigcrypt.o + $(CC) -o $(CHKPWD) $^ $(LDLIBS) + +unix_chkpwd.o: unix_chkpwd.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +md5_good.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -DHIGHFIRST -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_broken.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_good.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_broken.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +install: all + mkdir -p $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + install -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_auth.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_acct.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_passwd.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_session.so +endif + install $(CHKPWD) $(FAKEROOT)$(SUPLEMENTED) + +remove: + cd $(FAKEROOT)$(SECUREDIR) && rm -f $(LIBSHARED) + rm -f $(FAKEROOT)$(SUPLEMENTED)/$(CHKPWD) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) $(CHKPWD) *.o *.so core + +extraclean: clean + rm -f *~ *.a *.out *.bak + +.c.o: + $(CC) -c $(CFLAGS) $< + diff --git a/modules/pam_unix/README b/modules/pam_unix/README new file mode 100644 index 00000000..ad4bc35e --- /dev/null +++ b/modules/pam_unix/README @@ -0,0 +1,39 @@ +This is the README for pam_unix in Linux-PAM-0.67. +-------------------------------------------------- + +pam_unix now comes as one module pam_unix.so. + +The following links are left for compatibility with old versions: +pam_unix_auth: authentication module providing + pam_authenticate() and pam_setcred() hooks +pam_unix_sess: session module, providing session logging +pam_unix_acct: account management, providing shadow account + managment features, password aging etc.. +pam_unix_passwd: password updating facilities providing + cracklib password strength checking facilities. + +The following options are recognized: + debug - log more debugging info + audit - a little more extreme than debug + use_first_pass - don 't prompt the user for passwords + take them from PAM_ items instead + try_first_pass - don 't prompt the user for the passwords + unless PAM_(OLD)AUTHTOK is unset + use_authtok - like try_first_pass, but * fail * if the new + PAM_AUTHTOK has not been previously set. + (intended for stacking password modules only) + not_set_pass - don 't set the PAM_ items with the passwords + used by this module. + shadow - try to maintian a shadow based system. + md5 - when a user changes their password next, + encrypt it with the md5 algorithm. + bigcrypt - when a user changes their password next, + excrypt it with the DEC C2 - algorithm(0). + nodelay - used to prevent failed authentication + resulting in a delay of about 1 second. + nis - use NIS RPC for setting new password + remember=X - remember X old passwords, they are kept in + /etc/security/opasswd in MD5 crypted form + + invalid arguments are logged to syslog. + diff --git a/modules/pam_unix/bigcrypt.c b/modules/pam_unix/bigcrypt.c new file mode 100644 index 00000000..b1568d6b --- /dev/null +++ b/modules/pam_unix/bigcrypt.c @@ -0,0 +1,119 @@ +/* + * This function implements the "bigcrypt" algorithm specifically for + * Linux-PAM. + * + * This algorithm is algorithm 0 (default) shipped with the C2 secure + * implementation of Digital UNIX. + * + * Disclaimer: This work is not based on the source code to Digital + * UNIX, nor am I connected to Digital Equipment Corp, in any way + * other than as a customer. This code is based on published + * interfaces and reasonable guesswork. + * + * Description: The cleartext is divided into blocks of SEGMENT_SIZE=8 + * characters or less. Each block is encrypted using the standard UNIX + * libc crypt function. The result of the encryption for one block + * provides the salt for the suceeding block. + * + * Restrictions: The buffer used to hold the encrypted result is + * statically allocated. (see MAX_PASS_LEN below). This is necessary, + * as the returned pointer points to "static data that are overwritten + * by each call", (XPG3: XSI System Interface + Headers pg 109), and + * this is a drop in replacement for crypt(); + * + * Andy Phillips + */ + +#include +#include + +char *crypt(const char *key, const char *salt); +char *bigcrypt(const char *key, const char *salt); + +/* + * Max cleartext password length in segments of 8 characters this + * function can deal with (16 segments of 8 chars= max 128 character + * password). + */ + +#define MAX_PASS_LEN 16 +#define SEGMENT_SIZE 8 +#define SALT_SIZE 2 +#define KEYBUF_SIZE ((MAX_PASS_LEN*SEGMENT_SIZE)+SALT_SIZE) +#define ESEGMENT_SIZE 11 +#define CBUF_SIZE ((MAX_PASS_LEN*ESEGMENT_SIZE)+SALT_SIZE+1) + +char *bigcrypt(const char *key, const char *salt) +{ + static char dec_c2_cryptbuf[CBUF_SIZE]; /* static storage area */ + + unsigned long int keylen, n_seg, j; + char *cipher_ptr, *plaintext_ptr, *tmp_ptr, *salt_ptr; + char keybuf[KEYBUF_SIZE + 1]; + + D(("called with key='%s', salt='%s'.", key, salt)); + + /* reset arrays */ + memset(keybuf, 0, KEYBUF_SIZE + 1); + memset(dec_c2_cryptbuf, 0, CBUF_SIZE); + + /* fill KEYBUF_SIZE with key */ + strncpy(keybuf, key, KEYBUF_SIZE); + + /* deal with case that we are doing a password check for a + conventially encrypted password: the salt will be + SALT_SIZE+ESEGMENT_SIZE long. */ + if (strlen(salt) == (SALT_SIZE + ESEGMENT_SIZE)) + keybuf[SEGMENT_SIZE] = '\0'; /* terminate password early(?) */ + + keylen = strlen(keybuf); + + if (!keylen) { + n_seg = 1; + } else { + /* work out how many segments */ + n_seg = 1 + ((keylen - 1) / SEGMENT_SIZE); + } + + if (n_seg > MAX_PASS_LEN) + n_seg = MAX_PASS_LEN; /* truncate at max length */ + + /* set up some pointers */ + cipher_ptr = dec_c2_cryptbuf; + plaintext_ptr = keybuf; + + /* do the first block with supplied salt */ + tmp_ptr = crypt(plaintext_ptr, salt); /* libc crypt() */ + + /* and place in the static area */ + strncpy(cipher_ptr, tmp_ptr, 13); + cipher_ptr += ESEGMENT_SIZE + SALT_SIZE; + plaintext_ptr += SEGMENT_SIZE; /* first block of SEGMENT_SIZE */ + + /* change the salt (1st 2 chars of previous block) - this was found + by dowsing */ + + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + + /* so far this is identical to "return crypt(key, salt);", if + there is more than one block encrypt them... */ + + if (n_seg > 1) { + for (j = 2; j <= n_seg; j++) { + + tmp_ptr = crypt(plaintext_ptr, salt_ptr); + + /* skip the salt for seg!=0 */ + strncpy(cipher_ptr, (tmp_ptr + SALT_SIZE), ESEGMENT_SIZE); + + cipher_ptr += ESEGMENT_SIZE; + plaintext_ptr += SEGMENT_SIZE; + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + } + } + D(("key=|%s|, salt=|%s|\nbuf=|%s|\n", key, salt, dec_c2_cryptbuf)); + + /* this is the terminated encrypted password */ + + return dec_c2_cryptbuf; +} diff --git a/modules/pam_unix/lckpwdf.-c b/modules/pam_unix/lckpwdf.-c new file mode 100644 index 00000000..b5ff4585 --- /dev/null +++ b/modules/pam_unix/lckpwdf.-c @@ -0,0 +1,117 @@ +/* + * This is a hack, but until libc and glibc both include this function + * by default (libc only includes it if nys is not being used, at the + * moment, and glibc doesn't appear to have it at all) we need to have + * it here, too. :-( + * + * This should not become an official part of PAM. + * + * BEGIN_HACK + */ + +/* + * lckpwdf.c -- prevent simultaneous updates of password files + * + * Before modifying any of the password files, call lckpwdf(). It may block + * for up to 15 seconds trying to get the lock. Return value is 0 on success + * or -1 on failure. When you are done, call ulckpwdf() to release the lock. + * The lock is also released automatically when the process exits. Only one + * process at a time may hold the lock. + * + * These functions are supposed to be conformant with AT&T SVID Issue 3. + * + * Written by Marek Michalkiewicz , + * public domain. + */ + +#include +#include + +#define LOCKFILE "/etc/.pwd.lock" +#define TIMEOUT 15 + +static int lockfd = -1; + +static int set_close_on_exec(int fd) +{ + int flags = fcntl(fd, F_GETFD, 0); + if (flags == -1) + return -1; + flags |= FD_CLOEXEC; + return fcntl(fd, F_SETFD, flags); +} + +static int do_lock(int fd) +{ + struct flock fl; + + memset(&fl, 0, sizeof fl); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + return fcntl(fd, F_SETLKW, &fl); +} + +static void alarm_catch(int sig) +{ +/* does nothing, but fcntl F_SETLKW will fail with EINTR */ +} + +static int lckpwdf(void) +{ + struct sigaction act, oldact; + sigset_t set, oldset; + + if (lockfd != -1) + return -1; + + lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600); + if (lockfd == -1) + return -1; + if (set_close_on_exec(lockfd) == -1) + goto cleanup_fd; + + memset(&act, 0, sizeof act); + act.sa_handler = alarm_catch; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + if (sigaction(SIGALRM, &act, &oldact) == -1) + goto cleanup_fd; + + sigemptyset(&set); + sigaddset(&set, SIGALRM); + if (sigprocmask(SIG_UNBLOCK, &set, &oldset) == -1) + goto cleanup_sig; + + alarm(TIMEOUT); + if (do_lock(lockfd) == -1) + goto cleanup_alarm; + alarm(0); + sigprocmask(SIG_SETMASK, &oldset, NULL); + sigaction(SIGALRM, &oldact, NULL); + return 0; + + cleanup_alarm: + alarm(0); + sigprocmask(SIG_SETMASK, &oldset, NULL); + cleanup_sig: + sigaction(SIGALRM, &oldact, NULL); + cleanup_fd: + close(lockfd); + lockfd = -1; + return -1; +} + +static int ulckpwdf(void) +{ + unlink(LOCKFILE); + if (lockfd == -1) + return -1; + + if (close(lockfd) == -1) { + lockfd = -1; + return -1; + } + lockfd = -1; + return 0; +} +/* END_HACK */ diff --git a/modules/pam_unix/md5.c b/modules/pam_unix/md5.c new file mode 100644 index 00000000..d88d6810 --- /dev/null +++ b/modules/pam_unix/md5.c @@ -0,0 +1,256 @@ +/* + * $Id$ + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +#include +#include "md5.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32 t; + do { + t = (uint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32 *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Name(MD5Init)(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301U; + ctx->buf[1] = 0xefcdab89U; + ctx->buf[2] = 0x98badcfeU; + ctx->buf[3] = 0x10325476U; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Name(MD5Update)(struct MD5Context *ctx, unsigned const char *buf, unsigned len) +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Name(MD5Final)(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *) ctx->in)[14] = ctx->bits[0]; + ((uint32 *) ctx->in)[15] = ctx->bits[1]; + + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Name(MD5Transform)(uint32 buf[4], uint32 const in[16]) +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478U, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756U, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbU, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeU, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafU, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aU, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613U, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501U, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8U, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7afU, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1U, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beU, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122U, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193U, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eU, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821U, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562U, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340U, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51U, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaU, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dU, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453U, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681U, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8U, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6U, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6U, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87U, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edU, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905U, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8U, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9U, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aU, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942U, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681U, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122U, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cU, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44U, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9U, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60U, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70U, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6U, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faU, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085U, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05U, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039U, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5U, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8U, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665U, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244U, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97U, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7U, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039U, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3U, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92U, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dU, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1U, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fU, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0U, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314U, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1U, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82U, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235U, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbU, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391U, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/modules/pam_unix/md5.h b/modules/pam_unix/md5.h new file mode 100644 index 00000000..469e5bd1 --- /dev/null +++ b/modules/pam_unix/md5.h @@ -0,0 +1,35 @@ + +#ifndef MD5_H +#define MD5_H + +#ifdef __alpha +typedef unsigned int uint32; +#else +typedef unsigned long uint32; +#endif + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +void GoodMD5Init(struct MD5Context *); +void GoodMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void GoodMD5Final(unsigned char digest[16], struct MD5Context *); +void GoodMD5Transform(uint32 buf[4], uint32 const in[16]); +void BrokenMD5Init(struct MD5Context *); +void BrokenMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void BrokenMD5Final(unsigned char digest[16], struct MD5Context *); +void BrokenMD5Transform(uint32 buf[4], uint32 const in[16]); + +char *Goodcrypt_md5(const char *pw, const char *salt); +char *Brokencrypt_md5(const char *pw, const char *salt); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ + +typedef struct MD5Context MD5_CTX; + +#endif /* MD5_H */ diff --git a/modules/pam_unix/md5_crypt.c b/modules/pam_unix/md5_crypt.c new file mode 100644 index 00000000..a7243a2e --- /dev/null +++ b/modules/pam_unix/md5_crypt.c @@ -0,0 +1,149 @@ +/* + * $Id$ + * + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * Origin: Id: crypt.c,v 1.3 1995/05/30 05:42:22 rgrimes Exp + * + */ + +#include +#include "md5.h" + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} + +/* + * UNIX password + * + * Use MD5 for what it is best at... + */ + +char *MD5Name(crypt_md5)(const char *pw, const char *salt) +{ + const char *magic = "$1$"; + /* This string is magic for this algorithm. Having + * it this way, we can get get better later on */ + static char passwd[120], *p; + static const char *sp, *ep; + unsigned char final[16]; + int sl, pl, i, j; + MD5_CTX ctx, ctx1; + unsigned long l; + + /* Refine the Salt first */ + sp = salt; + + /* If it starts with the magic string, then skip that */ + if (!strncmp(sp, magic, strlen(magic))) + sp += strlen(magic); + + /* It stops at the first '$', max 8 chars */ + for (ep = sp; *ep && *ep != '$' && ep < (sp + 8); ep++) + continue; + + /* get the length of the true salt */ + sl = ep - sp; + + MD5Name(MD5Init)(&ctx); + + /* The password first, since that is what is most unknown */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)pw,strlen(pw)); + + /* Then our magic string */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)magic,strlen(magic)); + + /* Then the raw salt */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)sp,sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + MD5Name(MD5Init)(&ctx1); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + for (pl = strlen(pw); pl > 0; pl -= 16) + MD5Name(MD5Update)(&ctx,(unsigned const char *)final,pl>16 ? 16 : pl); + + /* Don't leave anything around in vm they could use. */ + memset(final, 0, sizeof final); + + /* Then something really weird... */ + for (j = 0, i = strlen(pw); i; i >>= 1) + if (i & 1) + MD5Name(MD5Update)(&ctx, (unsigned const char *)final+j, 1); + else + MD5Name(MD5Update)(&ctx, (unsigned const char *)pw+j, 1); + + /* Now make the output string */ + strcpy(passwd, magic); + strncat(passwd, sp, sl); + strcat(passwd, "$"); + + MD5Name(MD5Final)(final,&ctx); + + /* + * and now, just to make sure things don't run too fast + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for (i = 0; i < 1000; i++) { + MD5Name(MD5Init)(&ctx1); + if (i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + + if (i % 3) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + + if (i % 7) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + + if (i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + } + + p = passwd + strlen(passwd); + + l = (final[0] << 16) | (final[6] << 8) | final[12]; + to64(p, l, 4); + p += 4; + l = (final[1] << 16) | (final[7] << 8) | final[13]; + to64(p, l, 4); + p += 4; + l = (final[2] << 16) | (final[8] << 8) | final[14]; + to64(p, l, 4); + p += 4; + l = (final[3] << 16) | (final[9] << 8) | final[15]; + to64(p, l, 4); + p += 4; + l = (final[4] << 16) | (final[10] << 8) | final[5]; + to64(p, l, 4); + p += 4; + l = final[11]; + to64(p, l, 2); + p += 2; + *p = '\0'; + + /* Don't leave anything around in vm they could use. */ + memset(final, 0, sizeof final); + + return passwd; +} diff --git a/modules/pam_unix/need_nsl.sh b/modules/pam_unix/need_nsl.sh new file mode 100755 index 00000000..23f38ec0 --- /dev/null +++ b/modules/pam_unix/need_nsl.sh @@ -0,0 +1,7 @@ +#!/bin/sh +list=`/bin/ls /lib/libnsl.so.* 2> /dev/null` +if [ -z "$list" ]; then + echo no +else + echo yes +fi diff --git a/modules/pam_unix/pam_unix_acct.c b/modules/pam_unix/pam_unix_acct.c new file mode 100644 index 00000000..f86f56e5 --- /dev/null +++ b/modules/pam_unix/pam_unix_acct.c @@ -0,0 +1,210 @@ +/* + * Copyright Elliot Lee, 1996. All rights reserved. + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _BSD_SOURCE + +#ifdef linux +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for time() */ + + +#include + +/* indicate that the following groups are defined */ + +#define PAM_SM_ACCOUNT + +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for this entry-point to pass control to the + * account management module. + */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + const char *uname; + int retval, daysleft; + time_t curdays; + struct spwd *spent; + struct passwd *pwent; + char buf[80]; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (const void **) &uname); + D(("user = `%s'", uname)); + if (retval != PAM_SUCCESS || uname == NULL) { + _log_err(LOG_ALERT + ,"could not identify user (from uid=%d)" + ,getuid()); + return PAM_USER_UNKNOWN; + } + + pwent = getpwnam(uname); + if (!pwent) { + _log_err(LOG_ALERT + ,"could not identify user (from getpwnam(%s))" + ,uname); + return PAM_USER_UNKNOWN; + } + + if (!strcmp( pwent->pw_passwd, "*NP*" )) { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwent->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwent->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwent->pw_uid ) == -1) + return PAM_CRED_INSUFFICIENT; + } + } + spent = getspnam( uname ); + if (save_uid == pwent->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + + } else if (!strcmp( pwent->pw_passwd, "x" )) { + spent = getspnam(uname); + } else { + return PAM_SUCCESS; + } + + if (!spent) + return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */ + + curdays = time(NULL) / (60 * 60 * 24); + D(("today is %d, last change %d", curdays, spent->sp_lstchg)); + if ((curdays > spent->sp_expire) && (spent->sp_expire != -1) + && (spent->sp_lstchg != 0)) { + _log_err(LOG_NOTICE + ,"account %s has expired (account expired)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "Your account has expired; please contact your system administrator"); + D(("account expired")); + return PAM_ACCT_EXPIRED; + } + if ((curdays > (spent->sp_lstchg + spent->sp_max + spent->sp_inact)) + && (spent->sp_max != -1) && (spent->sp_inact != -1) + && (spent->sp_lstchg != 0)) { + _log_err(LOG_NOTICE + ,"account %s has expired (failed to change password)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "Your account has expired; please contact your system administrator"); + D(("account expired 2")); + return PAM_ACCT_EXPIRED; + } + D(("when was the last change")); + if (spent->sp_lstchg == 0) { + _log_err(LOG_NOTICE + ,"expired password for user %s (root enforced)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You are required to change your password immediately (root enforced)"); + D(("need a new password")); + return PAM_NEW_AUTHTOK_REQD; + } + if (((spent->sp_lstchg + spent->sp_max) < curdays) && (spent->sp_max != -1)) { + _log_err(LOG_DEBUG + ,"expired password for user %s (password aged)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You are required to change your password immediately (password aged)"); + D(("need a new password 2")); + return PAM_NEW_AUTHTOK_REQD; + } + if ((curdays > (spent->sp_lstchg + spent->sp_max - spent->sp_warn)) + && (spent->sp_max != -1) && (spent->sp_warn != -1)) { + daysleft = (spent->sp_lstchg + spent->sp_max) - curdays; + _log_err(LOG_DEBUG + ,"password for user %s will expire in %d days" + ,uname, daysleft); + snprintf(buf, 80, "Warning: your password will expire in %d day%.2s", + daysleft, daysleft == 1 ? "" : "s"); + _make_remark(pamh, ctrl, PAM_TEXT_INFO, buf); + } + + D(("all done")); + + return PAM_SUCCESS; +} + + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_acct_modstruct = { + "pam_unix_acct", + NULL, + NULL, + pam_sm_acct_mgmt, + NULL, + NULL, + NULL, +}; +#endif diff --git a/modules/pam_unix/pam_unix_auth.c b/modules/pam_unix/pam_unix_auth.c new file mode 100644 index 00000000..3c301df0 --- /dev/null +++ b/modules/pam_unix/pam_unix_auth.c @@ -0,0 +1,220 @@ +/* + * Copyright Alexander O. Yuriev, 1996. All rights reserved. + * NIS+ support by Thorsten Kukuk + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* #define DEBUG */ + +#ifdef linux +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* indicate the following groups are defined */ + +#define PAM_SM_AUTH + +#define _PAM_EXTERN_FUNCTIONS +#include +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for these entry-points to pass control to the + * authentication module. + */ + +/* Fun starts here :) + + * pam_sm_authenticate() performs UNIX/shadow authentication + * + * First, if shadow support is available, attempt to perform + * authentication using shadow passwords. If shadow is not + * available, or user does not have a shadow password, fallback + * onto a normal UNIX authentication + */ + +#define _UNIX_AUTHTOK "-UN*X-PASS" + +#define AUTH_RETURN \ +{ \ + if (on(UNIX_LIKE_AUTH, ctrl)) { \ + D(("recording return code for next time [%d]", \ + retval)); \ + pam_set_data(pamh, "unix_setcred_return", \ + (void *) &retval, NULL); \ + } \ + D(("done. [%s]", pam_strerror(pamh, retval))); \ + return retval; \ +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags + ,int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + const char *name, *p; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + /* get the user'name' */ + + retval = pam_get_user(pamh, &name, "login: "); + if (retval == PAM_SUCCESS) { + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't take + * any chances here. Require that the username starts with an + * alphanumeric character. + */ + if (name == NULL || !isalnum(*name)) { + _log_err(LOG_ERR, "bad username [%s]", name); + retval = PAM_USER_UNKNOWN; + AUTH_RETURN + } + if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) + D(("username [%s] obtained", name)); + } else { + D(("trouble reading username")); + if (retval == PAM_CONV_AGAIN) { + D(("pam_get_user/conv() function is not ready yet")); + /* it is safe to resume this function so we translate this + * retval to the value that indicates we're happy to resume. + */ + retval = PAM_INCOMPLETE; + } + AUTH_RETURN + } + + /* if this user does not have a password... */ + + if (_unix_blankpasswd(ctrl, name)) { + D(("user '%s' has blank passwd", name)); + name = NULL; + retval = PAM_SUCCESS; + AUTH_RETURN + } + /* get this user's authentication token */ + + retval = _unix_read_password(pamh, ctrl, NULL, "Password: ", NULL + ,_UNIX_AUTHTOK, &p); + if (retval != PAM_SUCCESS) { + if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_CRIT, "auth could not identify password for [%s]" + ,name); + } else { + D(("conversation function is not ready yet")); + /* + * it is safe to resume this function so we translate this + * retval to the value that indicates we're happy to resume. + */ + retval = PAM_INCOMPLETE; + } + name = NULL; + AUTH_RETURN + } + D(("user=%s, password=[%s]", name, p)); + + /* verify the password of this user */ + retval = _unix_verify_password(pamh, name, p, ctrl); + name = p = NULL; + + AUTH_RETURN +} + + +/* + * The only thing _pam_set_credentials_unix() does is initialization of + * UNIX group IDs. + * + * Well, everybody but me on linux-pam is convinced that it should not + * initialize group IDs, so I am not doing it but don't say that I haven't + * warned you. -- AOY + */ + +PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags + ,int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + /* FIXME: it shouldn't be necessary to parse the arguments again. The + only argument we need is UNIX_LIKE_AUTH: if it was set, + pam_get_data will succeed. If it wasn't, it will fail, and we + return PAM_SUCCESS. -SRL */ + ctrl = _set_ctrl(flags, NULL, argc, argv); + retval = PAM_SUCCESS; + + if (on(UNIX_LIKE_AUTH, ctrl)) { + int *pretval = &retval; + + D(("recovering return code from auth call")); + pam_get_data(pamh, "unix_setcred_return", (const void **) &pretval); + pam_set_data(pamh, "unix_setcred_return", NULL, NULL); + D(("recovered data indicates that old retval was %d", retval)); + } + return retval; +} + +#ifdef PAM_STATIC +struct pam_module _pam_unix_auth_modstruct = { + "pam_unix_auth", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; +#endif diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c new file mode 100644 index 00000000..cfa294f8 --- /dev/null +++ b/modules/pam_unix/pam_unix_passwd.c @@ -0,0 +1,985 @@ +/* + * Main coding by Elliot Lee , Red Hat Software. + * Copyright (C) 1996. + * Copyright (c) Jan Rêkorajski, 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _BSD_SOURCE +#define __USE_SVID + +#ifdef linux +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for time() */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_CRACKLIB +#include +#endif + +#include + +/* indicate the following groups are defined */ + +#define PAM_SM_PASSWORD + +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +#include "yppasswd.h" +#include "md5.h" +#include "support.h" + +#if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1)) +extern int getrpcport(const char *host, unsigned long prognum, + unsigned long versnum, unsigned int proto); +#endif /* GNU libc 2.1 */ + +/* + * PAM framework looks for these entry-points to pass control to the + * password changing module. + */ + +#ifdef NEED_LCKPWDF +#include "./lckpwdf.-c" +#endif + +extern char *bigcrypt(const char *key, const char *salt); + +/* + How it works: + Gets in username (has to be done) from the calling program + Does authentication of user (only if we are not running as root) + Gets new password/checks for sanity + Sets it. + */ + +/* passwd/salt conversion macros */ + +#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.') +#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') + +/* data tokens */ + +#define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS" +#define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS" + +#define MAX_PASSWD_TRIES 3 +#define PW_TMPFILE "/etc/npasswd" +#define SH_TMPFILE "/etc/nshadow" +#define CRACKLIB_DICTS "/usr/share/dict/cracklib_dict" +#define OPW_TMPFILE "/etc/security/nopasswd" +#define OLD_PASSWORDS_FILE "/etc/security/opasswd" + +/* + * i64c - convert an integer to a radix 64 character + */ +static int i64c(int i) +{ + if (i < 0) + return ('.'); + else if (i > 63) + return ('z'); + if (i == 0) + return ('.'); + if (i == 1) + return ('/'); + if (i >= 2 && i <= 11) + return ('0' - 2 + i); + if (i >= 12 && i <= 37) + return ('A' - 12 + i); + if (i >= 38 && i <= 63) + return ('a' - 38 + i); + return ('\0'); +} + +static char *crypt_md5_wrapper(const char *pass_new) +{ + /* + * Code lifted from Marek Michalkiewicz's shadow suite. (CG) + * removed use of static variables (AGM) + */ + + struct timeval tv; + MD5_CTX ctx; + unsigned char result[16]; + char *cp = (char *) result; + unsigned char tmp[16]; + int i; + char *x, *e = NULL; + + GoodMD5Init(&ctx); + gettimeofday(&tv, (struct timezone *) 0); + GoodMD5Update(&ctx, (void *) &tv, sizeof tv); + i = getpid(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + i = clock(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + GoodMD5Update(&ctx, result, sizeof result); + GoodMD5Final(tmp, &ctx); + strcpy(cp, "$1$"); /* magic for the MD5 */ + cp += strlen(cp); + for (i = 0; i < 8; i++) + *cp++ = i64c(tmp[i] & 077); + *cp = '\0'; + + /* no longer need cleartext */ + e = Goodcrypt_md5(pass_new, (const char *) result); + x = x_strdup(e); /* put e in malloc()ed memory */ + _pam_overwrite(e); /* clean up */ + + return x; +} + +static char *getNISserver(void) +{ + char *master; + char *domainname; + int port, err; + + if ((err = yp_get_default_domain(&domainname)) != 0) { + _log_err(LOG_WARNING, "can't get local yp domain: %s\n", + yperr_string(err)); + return NULL; + } + if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) { + _log_err(LOG_WARNING, "can't find the master ypserver: %s\n", + yperr_string(err)); + return NULL; + } + port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP); + if (port == 0) { + _log_err(LOG_WARNING, "yppasswdd not running on NIS master host\n"); + return NULL; + } + if (port >= IPPORT_RESERVED) { + _log_err(LOG_WARNING, "yppasswd daemon running on illegal port.\n"); + return NULL; + } + return master; +} + +static int check_old_password(const char *forwho, const char *newpass) +{ + static char buf[16384]; + char *s_luser, *s_uid, *s_npas, *s_pas; + int retval = PAM_SUCCESS; + FILE *opwfile; + + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (opwfile == NULL) + return PAM_AUTHTOK_ERR; + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, strlen(forwho))) { + buf[strlen(buf) - 1] = '\0'; + s_luser = strtok(buf, ":,"); + s_uid = strtok(NULL, ":,"); + s_npas = strtok(NULL, ":,"); + s_pas = strtok(NULL, ":,"); + while (s_pas != NULL) { + if (!strcmp(Goodcrypt_md5(newpass, s_pas), s_pas)) { + retval = PAM_AUTHTOK_ERR; + break; + } + s_pas = strtok(NULL, ":,"); + } + break; + } + } + fclose(opwfile); + + return retval; +} + +static int save_old_password(const char *forwho, const char *oldpass, int howmany) +{ + static char buf[16384]; + static char nbuf[16384]; + char *s_luser, *s_uid, *s_npas, *s_pas, *pass; + int retval = 0, npas; + FILE *pwfile, *opwfile; + int err = 0; + int oldmask; + int found = 0; + struct passwd *pwd = NULL; + + if (howmany < 0) + return retval; + + if (oldpass == NULL) + return retval; + + oldmask = umask(077); + pwfile = fopen(OPW_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(OPW_TMPFILE, 0, 0); + chmod(OPW_TMPFILE, 0600); + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, strlen(forwho))) { + buf[strlen(buf) - 1] = '\0'; + s_luser = strtok(buf, ":"); + s_uid = strtok(NULL, ":"); + s_npas = strtok(NULL, ":"); + s_pas = strtok(NULL, ":"); + npas = strtol(s_npas, NULL, 10) + 1; + while (npas > howmany) { + s_pas = strpbrk(s_pas, ","); + if (s_pas != NULL) + s_pas++; + npas--; + } + pass = crypt_md5_wrapper(oldpass); + if (s_pas == NULL) + sprintf(nbuf, "%s:%s:%d:%s\n", s_luser, s_uid, npas, pass); + else + sprintf(nbuf, "%s:%s:%d:%s,%s\n", s_luser, s_uid, npas, s_pas, pass); + if (fputs(nbuf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + break; + } + found = 1; + } else if (fputs(buf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + break; + } + } + fclose(opwfile); + if (!found) { + pwd = getpwnam(forwho); + if (pwd == NULL) { + retval = PAM_AUTHTOK_ERR; + err = 1; + } else { + pass = crypt_md5_wrapper(oldpass); + sprintf(nbuf, "%s:%d:1:%s\n", forwho, pwd->pw_uid, pass); + if (fputs(nbuf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + } + } + } + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to old passwords file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(OPW_TMPFILE, OLD_PASSWORDS_FILE); + else + unlink(OPW_TMPFILE); + + return retval; +} + +static int _update_passwd(const char *forwho, char *towhat) +{ + struct passwd *tmpent = NULL; + FILE *pwfile, *opwfile; + int retval = 0; + int err = 0; + int oldmask; + + oldmask = umask(077); + pwfile = fopen(PW_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen("/etc/passwd", "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(PW_TMPFILE, 0, 0); + chmod(PW_TMPFILE, 0644); + tmpent = fgetpwent(opwfile); + while (tmpent) { + if (!strcmp(tmpent->pw_name, forwho)) { + tmpent->pw_passwd = towhat; + } + if (putpwent(tmpent, pwfile)) { + fprintf(stderr, "error writing entry to password file: %s\n", + strerror(errno)); + err = 1; + retval = PAM_AUTHTOK_ERR; + break; + } + tmpent = fgetpwent(opwfile); + } + fclose(opwfile); + + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to password file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(PW_TMPFILE, "/etc/passwd"); + else + unlink(PW_TMPFILE); + + return retval; +} + +static int _update_shadow(const char *forwho, char *towhat) +{ + struct spwd *spwdent = NULL, *stmpent = NULL; + FILE *pwfile, *opwfile; + int retval = 0; + int err = 0; + int oldmask; + + spwdent = getspnam(forwho); + if (spwdent == NULL) + return PAM_USER_UNKNOWN; + oldmask = umask(077); + pwfile = fopen(SH_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen("/etc/shadow", "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(SH_TMPFILE, 0, 0); + chmod(SH_TMPFILE, 0600); + stmpent = fgetspent(opwfile); + while (stmpent) { + if (!strcmp(stmpent->sp_namp, forwho)) { + stmpent->sp_pwdp = towhat; + stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24); + + D(("Set password %s for %s", stmpent->sp_pwdp, forwho)); + } + if (putspent(stmpent, pwfile)) { + fprintf(stderr, "error writing entry to shadow file: %s\n", + strerror(errno)); + err = 1; + retval = PAM_AUTHTOK_ERR; + break; + } + stmpent = fgetspent(opwfile); + } + fclose(opwfile); + + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to shadow file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(SH_TMPFILE, "/etc/shadow"); + else + unlink(SH_TMPFILE); + + return retval; +} + +static int _do_setpass(const char *forwho, char *fromwhat, char *towhat, + unsigned int ctrl, int remember) +{ + struct passwd *pwd = NULL; + int retval = 0; + + D(("called")); + + setpwent(); + pwd = getpwnam(forwho); + endpwent(); + + if (pwd == NULL) + return PAM_AUTHTOK_ERR; + + if (on(UNIX_NIS, ctrl)) { + struct timeval timeout; + struct yppasswd yppwd; + CLIENT *clnt; + char *master; + int status; + int err = 0; + + /* Make RPC call to NIS server */ + if ((master = getNISserver()) == NULL) + return PAM_TRY_AGAIN; + + /* Initialize password information */ + yppwd.newpw.pw_passwd = pwd->pw_passwd; + yppwd.newpw.pw_name = pwd->pw_name; + yppwd.newpw.pw_uid = pwd->pw_uid; + yppwd.newpw.pw_gid = pwd->pw_gid; + yppwd.newpw.pw_gecos = pwd->pw_gecos; + yppwd.newpw.pw_dir = pwd->pw_dir; + yppwd.newpw.pw_shell = pwd->pw_shell; + yppwd.oldpass = fromwhat; + yppwd.newpw.pw_passwd = towhat; + + D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho)); + + /* The yppasswd.x file said `unix authentication required', + * so I added it. This is the only reason it is in here. + * My yppasswdd doesn't use it, but maybe some others out there + * do. --okir + */ + clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp"); + clnt->cl_auth = authunix_create_default(); + memset((char *) &status, '\0', sizeof(status)); + timeout.tv_sec = 25; + timeout.tv_usec = 0; + err = clnt_call(clnt, YPPASSWDPROC_UPDATE, + (xdrproc_t) xdr_yppasswd, (char *) &yppwd, + (xdrproc_t) xdr_int, (char *) &status, + timeout); + + if (err) { + clnt_perrno(err); + retval = PAM_TRY_AGAIN; + } else if (status) { + fprintf(stderr, "Error while changing NIS password.\n"); + retval = PAM_TRY_AGAIN; + } + printf("\nThe password has%s been changed on %s.\n", + (err || status) ? " not" : "", master); + + auth_destroy(clnt->cl_auth); + clnt_destroy(clnt); + if ((err || status) != 0) { + retval = PAM_TRY_AGAIN; + } +#ifdef DEBUG + sleep(5); +#endif + return retval; + } + /* first, save old password */ + if (save_old_password(forwho, fromwhat, remember)) { + return PAM_AUTHTOK_ERR; + } + if (on(UNIX_SHADOW, ctrl) || (strcmp(pwd->pw_passwd, "x") == 0)) { + retval = _update_shadow(forwho, towhat); + if (retval == PAM_SUCCESS) + retval = _update_passwd(forwho, "x"); + } else { + retval = _update_passwd(forwho, towhat); + } + + return retval; +} + +static int _unix_verify_shadow(const char *user, unsigned int ctrl) +{ + struct passwd *pwd = NULL; /* Password and shadow password */ + struct spwd *spwdent = NULL; /* file entries for the user */ + time_t curdays; + int retval = PAM_SUCCESS; + + /* UNIX passwords area */ + setpwent(); + pwd = getpwnam(user); /* Get password file entry... */ + endpwent(); + if (pwd == NULL) + return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */ + + if (strcmp(pwd->pw_passwd, "x") == 0) { + /* ...and shadow password file entry for this user, if shadowing + is enabled */ + setspent(); + spwdent = getspnam(user); + endspent(); + + if (spwdent == NULL) + return PAM_AUTHINFO_UNAVAIL; + } else { + if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */ + uid_t save_uid; + + save_uid = geteuid(); + seteuid (pwd->pw_uid); + spwdent = getspnam( user ); + seteuid (save_uid); + + if (spwdent == NULL) + return PAM_AUTHINFO_UNAVAIL; + } else + spwdent = NULL; + } + + if (spwdent != NULL) { + /* We have the user's information, now let's check if their account + has expired (60 * 60 * 24 = number of seconds in a day) */ + + if (off(UNIX__IAMROOT, ctrl)) { + /* Get the current number of days since 1970 */ + curdays = time(NULL) / (60 * 60 * 24); + if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min)) + && (spwdent->sp_min != -1)) + retval = PAM_AUTHTOK_ERR; + else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact)) + && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1) + && (spwdent->sp_lstchg != 0)) + /* + * Their password change has been put off too long, + */ + retval = PAM_ACCT_EXPIRED; + else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1) + && (spwdent->sp_lstchg != 0)) + /* + * OR their account has just plain expired + */ + retval = PAM_ACCT_EXPIRED; + } + } + return retval; +} + +static int _pam_unix_approve_pass(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *pass_old + ,const char *pass_new) +{ + const char *user; + char *remark = NULL; + int retval = PAM_SUCCESS; + + D(("&new=%p, &old=%p", pass_old, pass_new)); + D(("new=[%s]", pass_new)); + D(("old=[%s]", pass_old)); + + if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_DEBUG, "bad authentication token"); + } + _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ? + "No password supplied" : "Password unchanged"); + return PAM_AUTHTOK_ERR; + } + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place - AGM + */ + + retval = pam_get_item(pamh, PAM_USER, (const void **) &user); + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_ERR, "Can not get username"); + return PAM_AUTHTOK_ERR; + } + } + if (off(UNIX__IAMROOT, ctrl)) { +#ifdef USE_CRACKLIB + remark = FascistCheck(pass_new, CRACKLIB_DICTS); + D(("called cracklib [%s]", remark)); +#else + if (strlen(pass_new) < 6) + remark = "You must choose a longer password"; + D(("lenth check [%s]", remark)); +#endif + if (on(UNIX_REMEMBER_PASSWD, ctrl)) + if ((retval = check_old_password(user, pass_new)) != PAM_SUCCESS) + remark = "Password has been already used. Choose another."; + } + if (remark) { + _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark); + retval = PAM_AUTHTOK_ERR; + } + return retval; +} + + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl, lctrl; + int retval; + int remember = -1; + + /* */ + const char *user; + char *pass_old, *pass_new; + /* */ + + D(("called.")); + +#ifdef USE_LCKPWDF + /* our current locking system requires that we lock the + entire password database. This avoids both livelock + and deadlock. */ + lckpwdf(); +#endif + ctrl = _set_ctrl(flags, &remember, argc, argv); + + /* + * First get the name of a user + */ + retval = pam_get_user(pamh, &user, "Username: "); + if (retval == PAM_SUCCESS) { + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't take + * any chances here. Require that the username starts with an + * alphanumeric character. + */ + if (user == NULL || !isalnum(*user)) { + _log_err(LOG_ERR, "bad username [%s]", user); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "username [%s] obtained", user); + } else { + if (on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "password - could not identify user"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + + D(("Got username of %s", user)); + + /* + * This is not an AUTH module! + */ + if (on(UNIX__NONULL, ctrl)) + set(UNIX__NULLOK, ctrl); + + if (on(UNIX__PRELIM, ctrl)) { + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + char *Announce; + + D(("prelim check")); + + if (_unix_blankpasswd(ctrl, user)) { +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_SUCCESS; + } else if (off(UNIX__IAMROOT, ctrl)) { + + /* instruct user what is happening */ +#define greeting "Changing password for " + Announce = (char *) malloc(sizeof(greeting) + strlen(user)); + if (Announce == NULL) { + _log_err(LOG_CRIT, "password - out of memory"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_BUF_ERR; + } + (void) strcpy(Announce, greeting); + (void) strcpy(Announce + sizeof(greeting) - 1, user); +#undef greeting + + lctrl = ctrl; + set(UNIX__OLD_PASSWD, lctrl); + retval = _unix_read_password(pamh, lctrl + ,Announce + ,"(current) UNIX password: " + ,NULL + ,_UNIX_OLD_AUTHTOK + ,(const char **) &pass_old); + free(Announce); + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE + ,"password - (old) token not obtained"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + /* verify that this is the password for this user */ + + retval = _unix_verify_password(pamh, user, pass_old, ctrl); + } else { + D(("process run by root so do nothing this time around")); + pass_old = NULL; + retval = PAM_SUCCESS; /* root doesn't have too */ + } + + if (retval != PAM_SUCCESS) { + D(("Authentication failed")); + pass_old = NULL; +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); + pass_old = NULL; + if (retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "failed to set PAM_OLDAUTHTOK"); + } + retval = _unix_verify_shadow(user, ctrl); + if (retval == PAM_AUTHTOK_ERR) { + if (off(UNIX__IAMROOT, ctrl)) + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You must wait longer to change your password"); + else + retval = PAM_SUCCESS; + } + } else if (on(UNIX__UPDATE, ctrl)) { + /* + * tpass is used below to store the _pam_md() return; it + * should be _pam_delete()'d. + */ + + char *tpass = NULL; + int retry = 0; + + /* + * obtain the proposed password + */ + + D(("do update")); + + /* + * get the old token back. NULL was ok only if root [at this + * point we assume that this has already been enforced on a + * previous call to this function]. + */ + + if (off(UNIX_NOT_SET_PASS, ctrl)) { + retval = pam_get_item(pamh, PAM_OLDAUTHTOK + ,(const void **) &pass_old); + } else { + retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK + ,(const void **) &pass_old); + if (retval == PAM_NO_MODULE_DATA) { + retval = PAM_SUCCESS; + pass_old = NULL; + } + } + D(("pass_old [%s]", pass_old)); + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "user not authenticated"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + retval = _unix_verify_shadow(user, ctrl); + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "user not authenticated 2"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + D(("get new password now")); + + lctrl = ctrl; + + if (on(UNIX_USE_AUTHTOK, lctrl)) { + set(UNIX_USE_FIRST_PASS, lctrl); + } + retry = 0; + retval = PAM_AUTHTOK_ERR; + while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + retval = _unix_read_password(pamh, lctrl + ,NULL + ,"Enter new UNIX password: " + ,"Retype new UNIX password: " + ,_UNIX_NEW_AUTHTOK + ,(const char **) &pass_new); + + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_ALERT + ,"password - new password not obtained"); + } + pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + D(("returned to _unix_chauthtok")); + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (pass_new[0] == '\0') { /* "\0" password = NULL */ + pass_new = NULL; + } + retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new); + } + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "new password not acceptable"); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_new = pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + /* + * First we encrypt the new password. + */ + + if (on(UNIX_MD5_PASS, ctrl)) { + tpass = crypt_md5_wrapper(pass_new); + } else { + /* + * Salt manipulation is stolen from Rick Faith's passwd + * program. Sorry Rick :) -- alex + */ + + time_t tm; + char salt[3]; + + time(&tm); + salt[0] = bin_to_ascii(tm & 0x3f); + salt[1] = bin_to_ascii((tm >> 6) & 0x3f); + salt[2] = '\0'; + + if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) { + /* + * to avoid using the _extensions_ of the bigcrypt() + * function we truncate the newly entered password + */ + char *temp = malloc(9); + char *e; + + if (temp == NULL) { + _log_err(LOG_CRIT, "out of memory for password"); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_new = pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_BUF_ERR; + } + /* copy first 8 bytes of password */ + strncpy(temp, pass_new, 8); + temp[8] = '\0'; + + /* no longer need cleartext */ + e = bigcrypt(temp, salt); + tpass = x_strdup(e); + + _pam_overwrite(e); + _pam_delete(temp); /* tidy up */ + } else { + char *e; + + /* no longer need cleartext */ + e = bigcrypt(pass_new, salt); + tpass = x_strdup(e); + + _pam_overwrite(e); + } + } + + D(("password processed")); + + /* update the password database(s) -- race conditions..? */ + + retval = _do_setpass(user, pass_old, tpass, ctrl, remember); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + _pam_delete(tpass); + pass_old = pass_new = NULL; + } else { /* something has broken with the module */ + _log_err(LOG_ALERT, "password received unknown request"); + retval = PAM_ABORT; + } + + D(("retval was %d", retval)); + +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; +} + + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_passwd_modstruct = { + "pam_unix_passwd", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok, +}; +#endif + diff --git a/modules/pam_unix/pam_unix_sess.c b/modules/pam_unix/pam_unix_sess.c new file mode 100644 index 00000000..ec658453 --- /dev/null +++ b/modules/pam_unix/pam_unix_sess.c @@ -0,0 +1,138 @@ +/* + * Copyright Alexander O. Yuriev, 1996. All rights reserved. + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef linux +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* indicate the following groups are defined */ + +#define PAM_SM_SESSION + +#include +#include + +#ifndef LINUX_PAM +#include +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for these entry-points to pass control to the + * session module. + */ + +PAM_EXTERN int pam_sm_open_session(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + char *user_name, *service; + unsigned int ctrl; + int retval; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); + if (user_name == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; /* How did we get authenticated with + no username?! */ + } + retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service); + if (service == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "open_session - error recovering service"); + return PAM_SESSION_ERR; + } + _log_err(LOG_INFO, "(%s) session opened for user %s by %s(uid=%d)" + ,service, user_name + ,PAM_getlogin() == NULL ? "" : PAM_getlogin(), getuid()); + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + char *user_name, *service; + unsigned int ctrl; + int retval; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); + if (user_name == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "close_session - error recovering username"); + return PAM_SESSION_ERR; /* How did we get authenticated with + no username?! */ + } + retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service); + if (service == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "close_session - error recovering service"); + return PAM_SESSION_ERR; + } + _log_err(LOG_INFO, "(%s) session closed for user %s" + ,service, user_name); + + return PAM_SUCCESS; +} + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_session_modstruct = { + "pam_unix_session", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; +#endif + diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c new file mode 100644 index 00000000..610b29a7 --- /dev/null +++ b/modules/pam_unix/support.c @@ -0,0 +1,821 @@ +/* + * $Id$ + * + * Copyright information at end of file. + */ + +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "md5.h" +#include "support.h" + +extern char *crypt(const char *key, const char *salt); +extern char *bigcrypt(const char *key, const char *salt); + +/* syslogging function for errors and other information */ + +void _log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog("PAM_unix", LOG_CONS | LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* this is a front-end for module-application conversations */ + +static int converse(pam_handle_t * pamh, int ctrl, int nargs + ,struct pam_message **message + ,struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (retval == PAM_SUCCESS) { + + retval = conv->conv(nargs, (const struct pam_message **) message + ,response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_DEBUG, "conversation failure [%s]" + ,pam_strerror(pamh, retval)); + } + } else if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + ,pam_strerror(pamh, retval)); + } + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +int _make_remark(pam_handle_t * pamh, unsigned int ctrl + ,int type, const char *text) +{ + int retval = PAM_SUCCESS; + + if (off(UNIX__QUIET, ctrl)) { + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + + resp = NULL; + retval = converse(pamh, ctrl, 1, pmsg, &resp); + + if (resp) { + _pam_drop_reply(resp, 1); + } + } + return retval; +} + + /* + * Beacause getlogin() is fucked in a weird way, and + * sometimes it just don't work, we reimplement it here. + */ +char *PAM_getlogin(void) +{ + struct utmp *ut, line; + char *curr_tty, *retval; + static char curr_user[UT_NAMESIZE + 4]; + + retval = NULL; + + curr_tty = ttyname(0); + if (curr_tty != NULL) { + D(("PAM_getlogin ttyname: %s", curr_tty)); + curr_tty += 5; + setutent(); + strncpy(line.ut_line, curr_tty, sizeof line.ut_line); + if ((ut = getutline(&line)) != NULL) { + strncpy(curr_user, ut->ut_user, UT_NAMESIZE); + retval = curr_user; + } + endutent(); + } + D(("PAM_getlogin retval: %s", retval)); + + return retval; +} + +/* + * set the control flags for the UNIX module. + */ + +int _set_ctrl(int flags, int *remember, int argc, const char **argv) +{ + unsigned int ctrl; + + D(("called.")); + + ctrl = UNIX_DEFAULTS; /* the default selection of options */ + + /* set some flags manually */ + + if (getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)) { + D(("IAMROOT")); + set(UNIX__IAMROOT, ctrl); + } + if (flags & PAM_UPDATE_AUTHTOK) { + D(("UPDATE_AUTHTOK")); + set(UNIX__UPDATE, ctrl); + } + if (flags & PAM_PRELIM_CHECK) { + D(("PRELIM_CHECK")); + set(UNIX__PRELIM, ctrl); + } + if (flags & PAM_DISALLOW_NULL_AUTHTOK) { + D(("DISALLOW_NULL_AUTHTOK")); + set(UNIX__NONULL, ctrl); + } + if (flags & PAM_SILENT) { + D(("SILENT")); + set(UNIX__QUIET, ctrl); + } + /* now parse the arguments to this module */ + + while (argc-- > 0) { + int j; + + D(("pam_unix arg: %s", *argv)); + + for (j = 0; j < UNIX_CTRLS_; ++j) { + if (unix_args[j].token + && !strncmp(*argv, unix_args[j].token, strlen(unix_args[j].token))) { + break; + } + } + + if (j >= UNIX_CTRLS_) { + _log_err(LOG_ERR, "unrecognized option [%s]", *argv); + } else { + ctrl &= unix_args[j].mask; /* for turning things off */ + ctrl |= unix_args[j].flag; /* for turning things on */ + + if (remember != NULL) { + if (j == UNIX_REMEMBER_PASSWD) { + *remember = strtol(*argv + 9, NULL, 10); + if ((*remember == LONG_MIN) || (*remember == LONG_MAX)) + *remember = -1; + if (*remember > 400) + *remember = 400; + } + } + } + + ++argv; /* step to next argument */ + } + + /* auditing is a more sensitive version of debug */ + + if (on(UNIX_AUDIT, ctrl)) { + set(UNIX_DEBUG, ctrl); + } + /* return the set of flags */ + + D(("done.")); + return ctrl; +} + +static void _cleanup(pam_handle_t * pamh, void *x, int error_status) +{ + _pam_delete(x); +} + +/* ************************************************************** * + * Useful non-trivial functions * + * ************************************************************** */ + + /* + * the following is used to keep track of the number of times a user fails + * to authenticate themself. + */ + +#define FAIL_PREFIX "-UN*X-FAIL-" +#define UNIX_MAX_RETRIES 3 + +struct _pam_failed_auth { + char *user; /* user that's failed to be authenticated */ + char *name; /* attempt from user with name */ + int id; /* uid of name'd user */ + int count; /* number of failures so far */ +}; + +#ifndef PAM_DATA_REPLACE +#error "Need to get an updated libpam 0.52 or better" +#endif + +static void _cleanup_failures(pam_handle_t * pamh, void *fl, int err) +{ + int quiet; + const char *service = NULL; + struct _pam_failed_auth *failure; + + D(("called")); + + quiet = err & PAM_DATA_SILENT; /* should we log something? */ + err &= PAM_DATA_REPLACE; /* are we just replacing data? */ + failure = (struct _pam_failed_auth *) fl; + + if (failure != NULL) { + + if (!quiet && !err) { /* under advisement from Sun,may go away */ + + /* log the number of authentication failures */ + if (failure->count > 1) { + (void) pam_get_item(pamh, PAM_SERVICE + ,(const void **) &service); + _log_err(LOG_NOTICE + ,"%d more authentication failure%s; %s(uid=%d) -> " + "%s for %s service" + ,failure->count - 1, failure->count == 2 ? "" : "s" + ,failure->name + ,failure->id + ,failure->user + ,service == NULL ? "**unknown**" : service + ); + if (failure->count > UNIX_MAX_RETRIES) { + _log_err(LOG_ALERT + ,"service(%s) ignoring max retries; %d > %d" + ,service == NULL ? "**unknown**" : service + ,failure->count + ,UNIX_MAX_RETRIES); + } + } + } + _pam_delete(failure->user); /* tidy up */ + _pam_delete(failure->name); /* tidy up */ + free(failure); + } +} + +/* + * _unix_blankpasswd() is a quick check for a blank password + * + * returns TRUE if user does not have a password + * - to avoid prompting for one in such cases (CG) + */ + +int _unix_blankpasswd(unsigned int ctrl, const char *name) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + int retval; + + D(("called")); + + /* + * This function does not have to be too smart if something goes + * wrong, return FALSE and let this case to be treated somewhere + * else (CG) + */ + + if (on(UNIX__NONULL, ctrl)) + return 0; /* will fail but don't let on yet */ + + /* UNIX passwords area */ + pwd = getpwnam(name); /* Get password file entry... */ + + if (pwd != NULL) { + if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) + { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwd->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwd->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwd->pw_uid ) == -1) + /* Will fail elsewhere. */ + return 0; + } + } + + spwdent = getspnam( name ); + if (save_uid == pwd->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + } else if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + spwdent = getspnam(name); + } + if (spwdent) + salt = x_strdup(spwdent->sp_pwdp); + else + salt = x_strdup(pwd->pw_passwd); + } + /* Does this user have a password? */ + if (salt == NULL) { + retval = 0; + } else { + if (strlen(salt) == 0) + retval = 1; + else + retval = 0; + } + + /* tidy up */ + + if (salt) + _pam_delete(salt); + + return retval; +} + +/* + * verify the password of a user + */ + +#include +#include + +static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd, unsigned int ctrl) +{ + int retval, child, fds[2]; + + D(("called.")); + /* create a pipe for the password */ + if (pipe(fds) != 0) { + D(("could not make pipe")); + return PAM_AUTH_ERR; + } + + /* fork */ + child = fork(); + if (child == 0) { + static char *args[] = { NULL, NULL }; + static char *envp[] = { NULL }; + + /* XXX - should really tidy up PAM here too */ + + /* reopen stdin as pipe */ + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + + /* exec binary helper */ + args[0] = x_strdup(CHKPWD_HELPER); + execve(CHKPWD_HELPER, args, envp); + + /* should not get here: exit with error */ + D(("helper binary is not available")); + exit(PAM_AUTHINFO_UNAVAIL); + } else if (child > 0) { + /* wait for child */ + close(fds[0]); + /* if the stored password is NULL */ + if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */ + write(fds[1], "nullok\0\0", 8); + } else { + write(fds[1], "nonull\0\0", 8); + } + if (passwd != NULL) { /* send the password to the child */ + write(fds[1], passwd, strlen(passwd)+1); + passwd = NULL; + } else { + write(fds[1], "", 1); /* blank password */ + } + close(fds[1]); + (void) waitpid(child, &retval, 0); /* wait for helper to complete */ + retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR; + } else { + D(("fork failed")); + retval = PAM_AUTH_ERR; + } + + D(("returning %d", retval)); + return retval; +} + +int _unix_verify_password(pam_handle_t * pamh, const char *name + ,const char *p, unsigned int ctrl) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + char *pp = NULL; + char *data_name; + int retval; + + D(("called")); + +#ifdef HAVE_PAM_FAIL_DELAY + if (off(UNIX_NODELAY, ctrl)) { + D(("setting delay")); + (void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */ + } +#endif + + /* locate the entry for this user */ + + D(("locating user's record")); + + /* UNIX passwords area */ + pwd = getpwnam(name); /* Get password file entry... */ + + if (pwd != NULL) { + if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) + { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwd->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwd->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwd->pw_uid ) == -1) + return PAM_CRED_INSUFFICIENT; + } + } + + spwdent = getspnam( name ); + if (save_uid == pwd->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + } else if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + spwdent = getspnam(name); + } + if (spwdent) + salt = x_strdup(spwdent->sp_pwdp); + else + salt = x_strdup(pwd->pw_passwd); + } + + data_name = (char *) malloc(sizeof(FAIL_PREFIX) + strlen(name)); + if (data_name == NULL) { + _log_err(LOG_CRIT, "no memory for data-name"); + } else { + strcpy(data_name, FAIL_PREFIX); + strcpy(data_name + sizeof(FAIL_PREFIX) - 1, name); + } + + retval = PAM_SUCCESS; + if (pwd == NULL || salt == NULL || !strcmp(salt, "x")) { + if (geteuid()) { + /* we are not root perhaps this is the reason? Run helper */ + D(("running helper binary")); + retval = _unix_run_helper_binary(pamh, p, ctrl); + } else { + D(("user's record unavailable")); + if (on(UNIX_AUDIT, ctrl)) { + /* this might be a typo and the user has given a password + instead of a username. Careful with this. */ + _log_err(LOG_ALERT, "check pass; user (%s) unknown", name); + } else { + _log_err(LOG_ALERT, "check pass; user unknown"); + } + p = NULL; + retval = PAM_AUTHINFO_UNAVAIL; + } + } else { + if (!strlen(salt)) { + /* the stored password is NULL */ + if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */ + D(("user has empty password - access granted")); + retval = PAM_SUCCESS; + } else { + D(("user has empty password - access denied")); + retval = PAM_AUTH_ERR; + } + } else { + if (!strncmp(salt, "$1$", 3)) { + pp = Goodcrypt_md5(p, salt); + if (strcmp(pp, salt) != 0) { + pp = Brokencrypt_md5(p, salt); + } + } else { + pp = bigcrypt(p, salt); + } + p = NULL; /* no longer needed here */ + + /* the moment of truth -- do we agree with the password? */ + D(("comparing state of pp[%s] and salt[%s]", pp, salt)); + + if (strcmp(pp, salt) == 0) { + retval = PAM_SUCCESS; + } else { + retval = PAM_AUTH_ERR; + } + } + } + + if (retval == PAM_SUCCESS) { + if (data_name) /* reset failures */ + pam_set_data(pamh, data_name, NULL, _cleanup_failures); + } else { + if (data_name != NULL) { + struct _pam_failed_auth *new = NULL; + const struct _pam_failed_auth *old = NULL; + + /* get a failure recorder */ + + new = (struct _pam_failed_auth *) + malloc(sizeof(struct _pam_failed_auth)); + + if (new != NULL) { + + new->user = x_strdup(name); + new->id = getuid(); + new->name = x_strdup(PAM_getlogin()? PAM_getlogin() : ""); + + /* any previous failures for this user ? */ + pam_get_data(pamh, data_name, (const void **) &old); + + if (old != NULL) { + new->count = old->count + 1; + if (new->count >= UNIX_MAX_RETRIES) { + retval = PAM_MAXTRIES; + } + } else { + const char *service=NULL; + (void) pam_get_item(pamh, PAM_SERVICE, + (const void **)&service); + _log_err(LOG_NOTICE + ,"authentication failure; %s(uid=%d) -> " + "%s for %s service" + ,new->name + ,new->id + ,new->user + ,service == NULL ? "**unknown**":service + ); + new->count = 1; + } + + pam_set_data(pamh, data_name, new, _cleanup_failures); + + } else { + _log_err(LOG_CRIT, "no memory for failure recorder"); + } + } + } + + if (data_name) + _pam_delete(data_name); + if (salt) + _pam_delete(salt); + if (pp) + _pam_overwrite(pp); + + D(("done [%d].", retval)); + + return retval; +} + +/* + * obtain a password from the user + */ + +int _unix_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char *data_name + ,const char **pass) +{ + int authtok_flag; + int retval; + const char *item; + char *token; + + D(("called")); + + /* + * make sure nothing inappropriate gets returned + */ + + *pass = token = NULL; + + /* + * which authentication token are we getting? + */ + + authtok_flag = on(UNIX__OLD_PASSWD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK; + + /* + * should we obtain the password from a PAM item ? + */ + + if (on(UNIX_TRY_FIRST_PASS, ctrl) || on(UNIX_USE_FIRST_PASS, ctrl)) { + retval = pam_get_item(pamh, authtok_flag, (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _log_err(LOG_ALERT + ,"pam_get_item returned error to unix-read-password" + ); + return retval; + } else if (item != NULL) { /* we have a password! */ + *pass = item; + item = NULL; + return PAM_SUCCESS; + } else if (on(UNIX_USE_FIRST_PASS, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } else if (on(UNIX_USE_AUTHTOK, ctrl) + && off(UNIX__OLD_PASSWD, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * getting here implies we will have to get the password from the + * user directly. + */ + + { + struct pam_message msg[3], *pmsg[3]; + struct pam_response *resp; + int i, replies; + + /* prepare to converse */ + + if (comment != NULL && off(UNIX__QUIET, ctrl)) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = comment; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt1; + replies = 1; + + if (prompt2 != NULL) { + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt2; + ++replies; + } + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, ctrl, i, pmsg, &resp); + + if (resp != NULL) { + + /* interpret the response */ + + if (retval == PAM_SUCCESS) { /* a good conversation */ + + token = x_strdup(resp[i - replies].resp); + if (token != NULL) { + if (replies == 2) { + + /* verify that password entered correctly */ + if (!resp[i - 1].resp + || strcmp(token, resp[i - 1].resp)) { + _pam_delete(token); /* mistyped */ + retval = PAM_AUTHTOK_RECOVER_ERR; + _make_remark(pamh, ctrl + ,PAM_ERROR_MSG, MISTYPED_PASS); + } + } + } else { + _log_err(LOG_NOTICE + ,"could not recover authentication token"); + } + + } + /* + * tidy up the conversation (resp_retcode) is ignored + * -- what is it for anyway? AGM + */ + + _pam_drop_reply(resp, i); + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR : retval; + } + } + + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "unable to obtain a password"); + return retval; + } + /* 'token' is the entered password */ + + if (off(UNIX_NOT_SET_PASS, ctrl)) { + + /* we store this password as an item */ + + retval = pam_set_item(pamh, authtok_flag, token); + _pam_delete(token); /* clean it up */ + if (retval != PAM_SUCCESS + || (retval = pam_get_item(pamh, authtok_flag + ,(const void **) &item)) + != PAM_SUCCESS) { + + _log_err(LOG_CRIT, "error manipulating password"); + return retval; + + } + } else { + /* + * then store it as data specific to this module. pam_end() + * will arrange to clean it up. + */ + + retval = pam_set_data(pamh, data_name, (void *) token, _cleanup); + if (retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "error manipulating password data [%s]" + ,pam_strerror(pamh, retval)); + _pam_delete(token); + return retval; + } + item = token; + token = NULL; /* break link to password */ + } + + *pass = item; + item = NULL; /* break link to password */ + + return PAM_SUCCESS; +} + +/* ****************************************************************** * + * Copyright (c) Jan Rêkorajski 1999. + * Copyright (c) Andrew G. Morgan 1996-8. + * Copyright (c) Alex O. Yuriev, 1996. + * Copyright (c) Cristian Gafton 1996. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_unix/support.h b/modules/pam_unix/support.h new file mode 100644 index 00000000..68686738 --- /dev/null +++ b/modules/pam_unix/support.h @@ -0,0 +1,139 @@ +#ifndef _PAM_UNIX_SUPPORT_H +#define _PAM_UNIX_SUPPORT_H + + +/* + * here is the string to inform the user that the new passwords they + * typed were not the same. + */ + +#define MISTYPED_PASS "Sorry, passwords do not match" + +/* type definition for the control options */ + +typedef struct { + const char *token; + unsigned int mask; /* shall assume 32 bits of flags */ + unsigned int flag; +} UNIX_Ctrls; + +/* + * macro to determine if a given flag is on + */ + +#define on(x,ctrl) (unix_args[x].flag & ctrl) + +/* + * macro to determine that a given flag is NOT on + */ + +#define off(x,ctrl) (!on(x,ctrl)) + +/* + * macro to turn on/off a ctrl flag manually + */ + +#define set(x,ctrl) (ctrl = ((ctrl)&unix_args[x].mask)|unix_args[x].flag) +#define unset(x,ctrl) (ctrl &= ~(unix_args[x].flag)) + +/* the generic mask */ + +#define _ALL_ON_ (~0U) + +/* end of macro definitions definitions for the control flags */ + +/* ****************************************************************** * + * ctrl flags proper.. + */ + +/* + * here are the various options recognized by the unix module. They + * are enumerated here and then defined below. Internal arguments are + * given NULL tokens. + */ + +#define UNIX__OLD_PASSWD 0 /* internal */ +#define UNIX__VERIFY_PASSWD 1 /* internal */ +#define UNIX__IAMROOT 2 /* internal */ + +#define UNIX_AUDIT 3 /* print more things than debug.. + some information may be sensitive */ +#define UNIX_USE_FIRST_PASS 4 +#define UNIX_TRY_FIRST_PASS 5 +#define UNIX_NOT_SET_PASS 6 /* don't set the AUTHTOK items */ + +#define UNIX__PRELIM 7 /* internal */ +#define UNIX__UPDATE 8 /* internal */ +#define UNIX__NONULL 9 /* internal */ +#define UNIX__QUIET 10 /* internal */ +#define UNIX_USE_AUTHTOK 11 /* insist on reading PAM_AUTHTOK */ +#define UNIX_SHADOW 12 /* signal shadow on */ +#define UNIX_MD5_PASS 13 /* force the use of MD5 passwords */ +#define UNIX__NULLOK 14 /* Null token ok */ +#define UNIX_DEBUG 15 /* send more info to syslog(3) */ +#define UNIX_NODELAY 16 /* admin does not want a fail-delay */ +#define UNIX_NIS 17 /* wish to use NIS for pwd */ +#define UNIX_BIGCRYPT 18 /* use DEC-C2 crypt()^x function */ +#define UNIX_LIKE_AUTH 19 /* need to auth for setcred to work */ +#define UNIX_REMEMBER_PASSWD 20 /* Remember N previous passwords */ +/* -------------- */ +#define UNIX_CTRLS_ 21 /* number of ctrl arguments defined */ + + +static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = +{ +/* symbol token name ctrl mask ctrl * + * ----------------------- ------------------- --------------------- -------- */ + +/* UNIX__OLD_PASSWD */ {NULL, _ALL_ON_, 01}, +/* UNIX__VERIFY_PASSWD */ {NULL, _ALL_ON_, 02}, +/* UNIX__IAMROOT */ {NULL, _ALL_ON_, 04}, +/* UNIX_AUDIT */ {"audit", _ALL_ON_, 010}, +/* UNIX_USE_FIRST_PASS */ {"use_first_pass", _ALL_ON_^(060), 020}, +/* UNIX_TRY_FIRST_PASS */ {"try_first_pass", _ALL_ON_^(060), 040}, +/* UNIX_NOT_SET_PASS */ {"not_set_pass", _ALL_ON_, 0100}, +/* UNIX__PRELIM */ {NULL, _ALL_ON_^(0600), 0200}, +/* UNIX__UPDATE */ {NULL, _ALL_ON_^(0600), 0400}, +/* UNIX__NONULL */ {NULL, _ALL_ON_, 01000}, +/* UNIX__QUIET */ {NULL, _ALL_ON_, 02000}, +/* UNIX_USE_AUTHTOK */ {"use_authtok", _ALL_ON_, 04000}, +/* UNIX_SHADOW */ {"shadow", _ALL_ON_, 010000}, +/* UNIX_MD5_PASS */ {"md5", _ALL_ON_^(0400000), 020000}, +/* UNIX__NULLOK */ {"nullok", _ALL_ON_^(01000), 0}, +/* UNIX_DEBUG */ {"debug", _ALL_ON_, 040000}, +/* UNIX_NODELAY */ {"nodelay", _ALL_ON_, 0100000}, +/* UNIX_NIS */ {"nis", _ALL_ON_^(010000), 0200000}, +/* UNIX_BIGCRYPT */ {"bigcrypt", _ALL_ON_^(020000), 0400000}, +/* UNIX_LIKE_AUTH */ {"likeauth", _ALL_ON_, 01000000}, +/* UNIX_REMEMBER_PASSWD */ {"remember=", _ALL_ON_, 02000000}, +}; + +#define UNIX_DEFAULTS (unix_args[UNIX__NONULL].flag) + + +/* use this to free strings. ESPECIALLY password strings */ + +#define _pam_delete(xx) \ +{ \ + _pam_overwrite(xx); \ + _pam_drop(xx); \ +} + +extern char *PAM_getlogin(void); +extern void _log_err(int err, const char *format,...); +extern int _make_remark(pam_handle_t * pamh, unsigned int ctrl + ,int type, const char *text); +extern int _set_ctrl(int flags, int *remember, int argc, const char **argv); +extern int _unix_blankpasswd(unsigned int ctrl, const char *name); +extern int _unix_verify_password(pam_handle_t * pamh, const char *name + ,const char *p, unsigned int ctrl); +extern int _unix_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char *data_name + ,const char **pass); + +#endif /* _PAM_UNIX_SUPPORT_H */ + diff --git a/modules/pam_unix/unix_chkpwd.c b/modules/pam_unix/unix_chkpwd.c new file mode 100644 index 00000000..66c0ad7f --- /dev/null +++ b/modules/pam_unix/unix_chkpwd.c @@ -0,0 +1,324 @@ +/* + * $Id$ + * + * This program is designed to run setuid(root) or with sufficient + * privilege to read all of the unix password databases. It is designed + * to provide a mechanism for the current user (defined by this + * process' uid) to verify their own password. + * + * The password is read from the standard input. The exit status of + * this program indicates whether the user is authenticated or not. + * + * Copyright information is located at the end of the file. + * + */ + +#define _BSD_SOURCE +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXPASS 200 /* the maximum length of a password */ + +#include + +#include "md5.h" + +extern char *crypt(const char *key, const char *salt); +extern char *bigcrypt(const char *key, const char *salt); + +#define UNIX_PASSED 0 +#define UNIX_FAILED 1 + +/* syslogging function for errors and other information */ + +static void _log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static void su_sighandler(int sig) +{ + if (sig > 0) { + _log_err(LOG_NOTICE, "caught signal %d.", sig); + exit(sig); + } +} + +static void setup_signals(void) +{ + struct sigaction action; /* posix signal structure */ + + /* + * Setup signal handlers + */ + (void) memset((void *) &action, 0, sizeof(action)); + action.sa_handler = su_sighandler; + action.sa_flags = SA_RESETHAND; + (void) sigaction(SIGILL, &action, NULL); + (void) sigaction(SIGTRAP, &action, NULL); + (void) sigaction(SIGBUS, &action, NULL); + (void) sigaction(SIGSEGV, &action, NULL); + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + (void) sigaction(SIGTERM, &action, NULL); + (void) sigaction(SIGHUP, &action, NULL); + (void) sigaction(SIGINT, &action, NULL); + (void) sigaction(SIGQUIT, &action, NULL); +} + +static int _unix_verify_password(const char *name, const char *p, int opt) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + char *pp = NULL; + int retval = UNIX_FAILED; + + /* UNIX passwords area */ + setpwent(); + pwd = getpwnam(name); /* Get password file entry... */ + endpwent(); + if (pwd != NULL) { + if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + setspent(); + spwdent = getspnam(name); + endspent(); + if (spwdent != NULL) + salt = x_strdup(spwdent->sp_pwdp); + else + pwd = NULL; + } else { + if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */ + uid_t save_uid; + + save_uid = geteuid(); + seteuid(pwd->pw_uid); + spwdent = getspnam(name); + seteuid(save_uid); + + salt = x_strdup(spwdent->sp_pwdp); + } else { + salt = x_strdup(pwd->pw_passwd); + } + } + } + if (pwd == NULL || salt == NULL) { + _log_err(LOG_ALERT, "check pass; user unknown"); + p = NULL; + return retval; + } + + if (strlen(salt) == 0) + return (opt == 0) ? UNIX_FAILED : UNIX_PASSED; + + /* the moment of truth -- do we agree with the password? */ + retval = UNIX_FAILED; + if (!strncmp(salt, "$1$", 3)) { + pp = Goodcrypt_md5(p, salt); + if (strcmp(pp, salt) == 0) { + retval = UNIX_PASSED; + } else { + pp = Brokencrypt_md5(p, salt); + if (strcmp(pp, salt) == 0) + retval = UNIX_PASSED; + } + } else { + pp = bigcrypt(p, salt); + if (strcmp(pp, salt) == 0) { + retval = UNIX_PASSED; + } + } + p = NULL; /* no longer needed here */ + + /* clean up */ + { + char *tp = pp; + if (pp != NULL) { + while (tp && *tp) + *tp++ = '\0'; + } + pp = tp = NULL; + } + + return retval; +} + +static char *getuidname(uid_t uid) +{ + struct passwd *pw; +#if 0 + char *envname; + + envname = getenv("LOGNAME"); + if (envname == NULL) + return NULL; + + pw = getpwuid(uid); + if (pw == NULL) + return NULL; + + if (strcmp(envname, pw->pw_name)) + return NULL; + + return envname; +#else + static char username[32]; + + pw = getpwuid(uid); + if (pw == NULL) + return NULL; + + memset(username, 0, 32); + strncpy(username, pw->pw_name, 32); + username[31] = '\0'; + + return username; +#endif +} + +int main(int argc, char *argv[]) +{ + char pass[MAXPASS + 1]; + char option[8]; + int npass, opt; + int retval = UNIX_FAILED; + char *user; + + /* + * Catch or ignore as many signal as possible. + */ + setup_signals(); + + /* + * we establish that this program is running with non-tty stdin. + * this is to discourage casual use. It does *NOT* prevent an + * intruder from repeatadly running this program to determine the + * password of the current user (brute force attack, but one for + * which the attacker must already have gained access to the user's + * account). + */ + + if (isatty(STDIN_FILENO)) { + + _log_err(LOG_NOTICE + ,"inappropriate use of Unix helper binary [UID=%d]" + ,getuid()); + fprintf(stderr + ,"This binary is not designed for running in this way\n" + "-- the system administrator has been informed\n"); + sleep(10); /* this should discourage/annoy the user */ + return UNIX_FAILED; + } + /* + * determine the current user's name is + * 1. supplied as a environment variable as LOGNAME + * 2. the uid has to match the one associated with the LOGNAME. + */ + user = getuidname(getuid()); + + /* read the nollok/nonull option */ + + npass = read(STDIN_FILENO, option, 8); + + if (npass < 0) { + _log_err(LOG_DEBUG, "no option supplied"); + return UNIX_FAILED; + } else { + option[7] = '\0'; + if (strncmp(option, "nullok", 8) == 0) + opt = 1; + else + opt = 0; + } + + /* read the password from stdin (a pipe from the pam_unix module) */ + + npass = read(STDIN_FILENO, pass, MAXPASS); + + if (npass < 0) { /* is it a valid password? */ + + _log_err(LOG_DEBUG, "no password supplied"); + + } else if (npass >= MAXPASS) { + + _log_err(LOG_DEBUG, "password too long"); + + } else { + if (npass == 0) { + /* the password is NULL */ + + retval = _unix_verify_password(user, NULL, opt); + + } else { + /* does pass agree with the official one? */ + + pass[npass] = '\0'; /* NUL terminate */ + retval = _unix_verify_password(user, pass, opt); + + } + } + + memset(pass, '\0', MAXPASS); /* clear memory of the password */ + + /* return pass or fail */ + + return retval; +} + +/* + * Copyright (c) Andrew G. Morgan, 1996. All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_unix/yppasswd.h b/modules/pam_unix/yppasswd.h new file mode 100644 index 00000000..6b414be0 --- /dev/null +++ b/modules/pam_unix/yppasswd.h @@ -0,0 +1,51 @@ +/* + * yppasswdd + * Copyright 1994, 1995, 1996 Olaf Kirch, + * + * This program is covered by the GNU General Public License, version 2. + * It is provided in the hope that it is useful. However, the author + * disclaims ALL WARRANTIES, expressed or implied. See the GPL for details. + * + * This file was generated automatically by rpcgen from yppasswd.x, and + * editied manually. + */ + +#ifndef _YPPASSWD_H_ +#define _YPPASSWD_H_ + +#define YPPASSWDPROG ((u_long)100009) +#define YPPASSWDVERS ((u_long)1) +#define YPPASSWDPROC_UPDATE ((u_long)1) + +/* + * The password struct passed by the update call. I renamed it to + * xpasswd to avoid a type clash with the one defined in . + */ +#ifndef __sgi +typedef struct xpasswd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + char *pw_gecos; + char *pw_dir; + char *pw_shell; +} xpasswd; + +#else +#include +typedef struct xpasswd xpasswd; +#endif + +/* The updated password information, plus the old password. + */ +typedef struct yppasswd { + char *oldpass; + xpasswd newpw; +} yppasswd; + +/* XDR encoding/decoding routines */ +bool_t xdr_xpasswd(XDR * xdrs, xpasswd * objp); +bool_t xdr_yppasswd(XDR * xdrs, yppasswd * objp); + +#endif /* _YPPASSWD_H_ */ diff --git a/modules/pam_unix/yppasswd_xdr.c b/modules/pam_unix/yppasswd_xdr.c new file mode 100644 index 00000000..eeb36423 --- /dev/null +++ b/modules/pam_unix/yppasswd_xdr.c @@ -0,0 +1,41 @@ +/* + * yppasswdd + * Copyright 1994, 1995, 1996 Olaf Kirch, + * + * This program is covered by the GNU General Public License, version 2. + * It is provided in the hope that it is useful. However, the author + * disclaims ALL WARRANTIES, expressed or implied. See the GPL for details. + * + * This file was generated automatically by rpcgen from yppasswd.x, and + * editied manually. + */ + +#ifdef linux +# define _GNU_SOURCE +# include +#endif + +#include +#include +#include +#include "yppasswd.h" + +bool_t +xdr_xpasswd(XDR * xdrs, xpasswd * objp) +{ + return xdr_string(xdrs, &objp->pw_name, ~0) + && xdr_string(xdrs, &objp->pw_passwd, ~0) + && xdr_int(xdrs, &objp->pw_uid) + && xdr_int(xdrs, &objp->pw_gid) + && xdr_string(xdrs, &objp->pw_gecos, ~0) + && xdr_string(xdrs, &objp->pw_dir, ~0) + && xdr_string(xdrs, &objp->pw_shell, ~0); +} + + +bool_t +xdr_yppasswd(XDR * xdrs, yppasswd * objp) +{ + return xdr_string(xdrs, &objp->oldpass, ~0) + && xdr_xpasswd(xdrs, &objp->newpw); +} diff --git a/modules/pam_userdb/.cvsignore b/modules/pam_userdb/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_userdb/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_userdb/Makefile b/modules/pam_userdb/Makefile new file mode 100644 index 00000000..39226242 --- /dev/null +++ b/modules/pam_userdb/Makefile @@ -0,0 +1,81 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). + +# $Id$ +# Created by Cristian Gafton + +WHICH_DB=$(shell ./libdbfound.sh) +ifeq ($(WHICH_DB),none) + +include ../dont_makefile + +else + +TITLE=pam_userdb + +LIBSRC = $(TITLE).c conv.c +LIBOBJ = $(TITLE).o conv.o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +#LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +EXTRALS += -ldb +CFLAGS += $(WHICH_DB) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +#static/%.o : %.c +# $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(EXTRALS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ *.so + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + +.PHONY: register + +test: + install -m 755 $(TITLE).so /tmp + +endif diff --git a/modules/pam_userdb/README b/modules/pam_userdb/README new file mode 100644 index 00000000..09d65edd --- /dev/null +++ b/modules/pam_userdb/README @@ -0,0 +1,30 @@ +pam_userdb: + Look up users in a .db database and verify their password against + what is contained in that database. + +RECOGNIZED ARGUMENTS: + debug write a message to syslog indicating success or + failure. + + db=[path] use the [path] database for performing lookup. There + is no default; the module will return PAM_IGNORE if + no database is provided. + + icase make the password verification to be case insensitive + (ie when working with registration numbers and such) + + dump dump all the entries in the database to the log (eek, + don't do this by default!) + +MODULE SERVICES PROVIDED: + auth _authetication and _setcred (blank) + +EXAMPLE USE: + auth sufficient pam_userdb.so icase db=/tmp/dbtest.db + +AUTHOR: + Cristian Gafton + + + +$Id$ diff --git a/modules/pam_userdb/conv.c b/modules/pam_userdb/conv.c new file mode 100644 index 00000000..0f13d03a --- /dev/null +++ b/modules/pam_userdb/conv.c @@ -0,0 +1,125 @@ +/* + * Conversation related functions + */ + +/* $Id */ +/* Copyright at the end of the file */ + +#define _BSD_SOURCE + +#include +#include + +#include +#include + +#include "pam_userdb.h" + +/* + * dummy conversation function sending exactly one prompt + * and expecting exactly one response from the other party + */ +static int converse(pam_handle_t *pamh, + struct pam_message **message, + struct pam_response **response) +{ + int retval; + const struct pam_conv *conv; + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ; + if (retval == PAM_SUCCESS) + retval = conv->conv(1, (const struct pam_message **)message, + response, conv->appdata_ptr); + + return retval; /* propagate error status */ +} + + +static char *_pam_delete(register char *xx) +{ + _pam_overwrite(xx); + _pam_drop(xx); + return NULL; +} + +/* + * This is a conversation function to obtain the user's password + */ +int conversation(pam_handle_t *pamh) +{ + struct pam_message msg[2],*pmsg[2]; + struct pam_response *resp; + int retval; + char * token = NULL; + + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + msg[0].msg = "Password: "; + + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, pmsg, &resp); + + if (resp != NULL) { + const char * item; + /* interpret the response */ + if (retval == PAM_SUCCESS) { /* a good conversation */ + token = x_strdup(resp[0].resp); + if (token == NULL) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + + /* set the auth token */ + retval = pam_set_item(pamh, PAM_AUTHTOK, token); + token = _pam_delete(token); /* clean it up */ + if ( (retval != PAM_SUCCESS) || + (retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&item)) + != PAM_SUCCESS ) { + return retval; + } + + _pam_drop_reply(resp, 1); + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR:retval ; + } + + return retval; +} + +/* + * Copyright (c) Cristian Gafton , 1999 + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_userdb/create.pl b/modules/pam_userdb/create.pl new file mode 100644 index 00000000..046b55f0 --- /dev/null +++ b/modules/pam_userdb/create.pl @@ -0,0 +1,23 @@ +#!/usr/bin/perl +# this program creates a database in ARGV[1] from pairs given on +# stdandard input +# +# $Id$ + +use DB_File; + +my $database = $ARGV[0]; +die "Use: check,pl \n" unless ($database); +print "Using database: $database\n"; + +my %lusers = (); + +tie %lusers, 'DB_File', $database, O_RDWR|O_CREAT, 0644, $DB_HASH ; +while () { + my ($user, $pass) = split; + + $lusers{$user} = $pass; +} +untie %lusers; + + diff --git a/modules/pam_userdb/libdbfound.sh b/modules/pam_userdb/libdbfound.sh new file mode 100755 index 00000000..d6a1723b --- /dev/null +++ b/modules/pam_userdb/libdbfound.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +if [ -f "/usr/include/ndbm.h" ]; then + echo "-DUSE_NDBM_H" + exit 0 +fi + +list=`/bin/ls /lib/libdb.so.* 2> /dev/null` +if [ -n "$list" ]; then + echo "" + exit 0 +fi + +echo "none" +exit 0 diff --git a/modules/pam_userdb/pam_userdb.c b/modules/pam_userdb/pam_userdb.c new file mode 100644 index 00000000..20ca5d10 --- /dev/null +++ b/modules/pam_userdb/pam_userdb.c @@ -0,0 +1,302 @@ +/* pam_userdb module */ + +/* + * $Id$ + * Written by Cristian Gafton 1996/09/10 + * See the end of the file for Copyright Information + */ + +#define _GNU_SOURCE +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pam_userdb.h" + +#ifdef USE_NDBM_H +# include +#else /* USE_NDBM_H */ +# define DB_DBM_HSEARCH 1 /* use the dbm interface */ +# include +#endif /* USE_NDBM_H */ + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT + +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +char * database = NULL; +static int ctrl = 0; + +static int _pam_parse(int argc, const char **argv) +{ + /* step through arguments */ + for (ctrl = 0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strcasecmp(*argv, "icase")) + ctrl |= PAM_ICASE_ARG; + else if (!strcasecmp(*argv, "dump")) + ctrl |= PAM_DUMP_ARG; + else if (!strncasecmp(*argv,"db=", 3)) { + database = strdup((*argv) + 3); + if (database == NULL) + _pam_log(LOG_ERR, "pam_parse: could not parse argument \"%s\"", + *argv); + } else { + _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv); + } + } + + return ctrl; +} + + +/* + * Looks up an user name in a database and checks the password + * + * return values: + * 1 = User not found + * 0 = OK + * -1 = Password incorrect + * -2 = System error + */ +static int user_lookup(const char *user, const char *pass) +{ + DBM *dbm; + datum key, data; + + /* Open the DB file. */ + dbm = dbm_open(database, O_RDONLY, 0644); + if (dbm == NULL) { + _pam_log(LOG_ERR, "user_lookup: could not open database `%s'", + database); + return -2; + } + + if (ctrl &PAM_DUMP_ARG) { + _pam_log(LOG_INFO, "Database dump:"); + for (key = dbm_firstkey(dbm); key.dptr != NULL; + key = dbm_nextkey(dbm)) { + data = dbm_fetch(dbm, key); + _pam_log(LOG_INFO, "key[len=%d] = `%s', data[len=%d] = `%s'", + key.dsize, key.dptr, data.dsize, data.dptr); + } + } + /* do some more init work */ + + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + key.dptr = x_strdup(user); + key.dsize = strlen(user); + user = NULL; + + if (key.dptr) { + data = dbm_fetch(dbm, key); + memset(key.dptr, 0, key.dsize); + free(key.dptr); + } + + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_INFO, "password in database is [%p]`%s', len is %d", + data.dptr, (char *) data.dptr, data.dsize); + } + + if (data.dptr != NULL) { + int compare = 0; + /* bingo, got it */ + if (ctrl & PAM_ICASE_ARG) + compare = strncasecmp(pass, data.dptr, data.dsize); + else + compare = strncmp(pass, data.dptr, data.dsize); + dbm_close(dbm); + if (compare == 0) + return 0; /* match */ + else + return -1; /* wrong */ + } else { + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_INFO, "error returned by dbm_fetch: %s", + strerror(errno)); + } + dbm_close(dbm); + /* probably we should check dbm_error() here */ + return 1; /* not found */ + } + + /* NOT REACHED */ + return -2; +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + const char *username; + const char *password; + int retval = PAM_AUTH_ERR; + + /* parse arguments */ + ctrl = _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + /* Converse just to be sure we have the password */ + retval = conversation(pamh); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "could not obtain password for `%s'", + username); + return -2; + } + + /* Get the password */ + retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "Could not retrive user's password"); + return -2; + } + + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_INFO, "Verify user `%s' with password `%s'", + username, password); + + /* Now use the username to look up password in the database file */ + retval = user_lookup(username, password); + switch (retval) { + case -2: + /* some sort of system error. The log was already printed */ + return PAM_SERVICE_ERR; + case -1: + /* incorrect password */ + _pam_log(LOG_WARNING, + "user `%s' denied access (incorrect password)", + username); + return PAM_AUTH_ERR; + case 1: + /* the user does not exist in the database */ + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE, "user `%s' not found in the database", + username); + return PAM_USER_UNKNOWN; + case 0: + /* Otherwise, the authentication looked good */ + _pam_log(LOG_NOTICE, "user '%s' granted acces", username); + return PAM_SUCCESS; + default: + /* we don't know anything about this return value */ + _pam_log(LOG_ERR, + "internal module error (retval = %d, user = `%s'", + retval, username); + return PAM_SERVICE_ERR; + } + + /* should not be reached */ + return PAM_IGNORE; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return PAM_SUCCESS; +} + +PAM_EXTERN +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_wheel_modstruct = { + MODULE_NAME, + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* + * Copyright (c) Cristian Gafton , 1999 + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_userdb/pam_userdb.h b/modules/pam_userdb/pam_userdb.h new file mode 100644 index 00000000..911a7622 --- /dev/null +++ b/modules/pam_userdb/pam_userdb.h @@ -0,0 +1,61 @@ + +#ifndef _PAM_USERSDB_H +#define _PAM_USERSDB_H +/* $Id$ */ + +/* Header files */ +#include + +/* argument parsing */ +#define PAM_DEBUG_ARG 0x0001 +#define PAM_ICASE_ARG 0x0002 +#define PAM_DUMP_ARG 0x0004 + +/* Useful macros */ +#define x_strdup(s) ( (s) ? strdup(s):NULL ) + +/* The name of the module we are compiling */ +#ifndef MODULE_NAME +#define MODULE_NAME "pam_userdb" +#endif /* MODULE_NAME */ + +/* function prototypes */ +int conversation(pam_handle_t *); + +#endif /* _PAM_USERSDB_H */ + +/* + * Copyright (c) Cristian Gafton , 1999 + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/pam_warn/.cvsignore b/modules/pam_warn/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_warn/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_warn/Makefile b/modules/pam_warn/Makefile new file mode 100644 index 00000000..7b48689c --- /dev/null +++ b/modules/pam_warn/Makefile @@ -0,0 +1,102 @@ +# +# $Id$ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# $Log$ +# Revision 1.1 2000/06/20 22:12:10 agmorgan +# Initial revision +# +# Revision 1.1.1.1 1998/07/12 05:17:17 morgan +# Linux PAM sources pre-0.66 +# +# Revision 1.2 1997/04/05 06:20:16 morgan +# fixed fakeroot +# +# Revision 1.1 1996/12/01 03:12:22 morgan +# Initial revision +# +# +# Created by Andrew Morgan 1996/11/14 +# + +TITLE=pam_warn + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_warn/README b/modules/pam_warn/README new file mode 100644 index 00000000..6d484bdf --- /dev/null +++ b/modules/pam_warn/README @@ -0,0 +1,26 @@ +# $Id$ +# + +This module is an authentication module that does not authenticate. +Instead it always returns PAM_IGNORE, indicating that it does not want +to affect the authentication process. + +Its purpose is to log a message to the syslog indicating the +pam_item's available at the time it was invoked. It is a diagnostic +tool. + +Recognized arguments: + + + +module services provided: + + auth _authenticate and _setcred (blank) + acct _acct_mgmt [mapped to _authenticate] + session _open_session and + _close_session [mapped to _authenticate ] + password _chauthtok [mapped to _authenticate] + + +Andrew Morgan +1996/11/14 diff --git a/modules/pam_warn/pam_warn.c b/modules/pam_warn/pam_warn.c new file mode 100644 index 00000000..2e390ca4 --- /dev/null +++ b/modules/pam_warn/pam_warn.c @@ -0,0 +1,132 @@ +/* pam_warn module */ + +/* + * $Id$ + * + * Written by Andrew Morgan 1996/3/11 + */ + +#define _BSD_SOURCE + +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH +#define PAM_SM_PASSWORD + +#include + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-warn", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc + , const char **argv) +{ + const char *service=NULL, *user=NULL, *terminal=NULL + , *rhost=NULL, *ruser=NULL; + + (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&service); + (void) pam_get_item(pamh, PAM_TTY, (const void **)&terminal); + _pam_log(LOG_NOTICE, "service: %s [on terminal: %s]" + , service ? service : "" + , terminal ? terminal : "" + ); + (void) pam_get_user(pamh, &user, "Who are you? "); + (void) pam_get_item(pamh, PAM_RUSER, (const void **)&ruser); + (void) pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); + _pam_log(LOG_NOTICE, "user: (uid=%d) -> %s [remote: %s@%s]" + , getuid() + , user ? user : "" + , ruser ? ruser : "?nobody" + , rhost ? rhost : "?nowhere" + ); + + /* we are just a fly on the wall */ + + return PAM_IGNORE; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + , const char **argv) +{ + return PAM_IGNORE; +} + +/* password updating functions */ + +PAM_EXTERN +int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc + , const char **argv) +{ + /* map to the authentication function... */ + + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +PAM_EXTERN int +pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + /* map to the authentication function... */ + + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +PAM_EXTERN int +pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + /* map to the authentication function... */ + + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +PAM_EXTERN int +pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + /* map to the authentication function... */ + + return pam_sm_authenticate(pamh, flags, argc, argv); +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_warn_modstruct = { + "pam_warn", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok, +}; + +#endif + +/* end of module definition */ diff --git a/modules/pam_wheel/.cvsignore b/modules/pam_wheel/.cvsignore new file mode 100644 index 00000000..380a834a --- /dev/null +++ b/modules/pam_wheel/.cvsignore @@ -0,0 +1 @@ +dynamic diff --git a/modules/pam_wheel/Makefile b/modules/pam_wheel/Makefile new file mode 100644 index 00000000..a262babf --- /dev/null +++ b/modules/pam_wheel/Makefile @@ -0,0 +1,86 @@ +# +# This Makefile controls a build process of $(TITLE) module for +# Linux-PAM. You should not modify this Makefile (unless you know +# what you are doing!). +# +# Created by Cristian Gafton 1996/09/10 +# + +TITLE=pam_wheel + +# + +LIBSRC = $(TITLE).c +LIBOBJ = $(TITLE).o +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +#LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +#static/%.o : %.c +# $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +#ifdef STATIC +#LIBSTATIC = lib$(TITLE).o +#endif + +####################### don't edit below ####################### + +dummy: + + @echo "**** This is not a top-level Makefile " + exit + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +#ifdef STATIC +# $(MKDIR) ./static +#endif + +register: +#ifdef STATIC +# ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +#endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(EXTRALS) +endif + +#ifdef STATIC +#$(LIBOBJS): $(LIBSRC) +# +#$(LIBSTATIC): $(LIBOBJS) +# $(LD) -r -o $@ $(LIBOBJS) $(EXTRALS) +#endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ *.so + +extraclean: clean + rm -f *.a *.o *.so *.bak dynamic/* static/* + +.c.o: + $(CC) $(CFLAGS) -c $< + diff --git a/modules/pam_wheel/README b/modules/pam_wheel/README new file mode 100644 index 00000000..336bb31e --- /dev/null +++ b/modules/pam_wheel/README @@ -0,0 +1,33 @@ + +pam_wheel: + only permit root authentication too members of wheel group + +RECOGNIZED ARGUMENTS: + debug write a message to syslog indicating success or + failure. + + use_uid the check for wheel membership will be done against + the current uid instead of the original one + (useful when jumping with su from one account to + another for example) + + trust the pam_wheel module will return PAM_SUCCESS instead + of PAM_IGNORE if the user is a member of the wheel + group (thus with a little play stacking the modules + the wheel members may be able to su to root without + being prompted for a passwd). + + deny Reverse the sense of the auth operation: if the user + is trying to get UID 0 access and is a member of the + wheel group, deny access (well, kind of nonsense, but + for use in conjunction with 'group' argument... :-) + + group=xxxx Instead of checking the GID 0 group, use the xxxx + group to perform the authentification. + +MODULE SERVICES PROVIDED: + auth _authetication and _setcred (blank) + +AUTHOR: + Cristian Gafton + diff --git a/modules/pam_wheel/pam_wheel.c b/modules/pam_wheel/pam_wheel.c new file mode 100644 index 00000000..417fa5b8 --- /dev/null +++ b/modules/pam_wheel/pam_wheel.c @@ -0,0 +1,275 @@ +/* pam_wheel module */ + +/* + * Written by Cristian Gafton 1996/09/10 + * See the end of the file for Copyright Information + * + * + * 1.2 - added 'deny' and 'group=' options + * 1.1 - added 'trust' option + * 1.0 - the code is working for at least another person, so... :-) + * 0.1 - use vsyslog instead of vfprintf/syslog in _pam_log + * - return PAM_IGNORE on success (take care of sloppy sysadmins..) + * - use pam_get_user instead of pam_get_item(...,PAM_USER,...) + * - a new arg use_uid to auth the current uid instead of the + * initial (logged in) one. + * 0.0 - first release + * + * TODO: + * - try to use make_remark from pam_unix/support.c + * - consider returning on failure PAM_FAIL_NOW if the user is not + * a wheel member. + */ + +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_AUTH + +#include + +/* variables */ +static char use_group[BUFSIZ]; + +/* some syslogging */ + +static void _pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + openlog("PAM-Wheel", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* checks if a user is on a list of members of the GID 0 group */ + +static int is_on_list(char * const *list, const char *member) +{ + while (*list) { + if (strcmp(*list, member) == 0) + return 1; + list++; + } + return 0; +} + +/* argument parsing */ + +#define PAM_DEBUG_ARG 0x0001 +#define PAM_USE_UID_ARG 0x0002 +#define PAM_TRUST_ARG 0x0004 +#define PAM_DENY_ARG 0x0010 + +static int _pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strcmp(*argv,"use_uid")) + ctrl |= PAM_USE_UID_ARG; + else if (!strcmp(*argv,"trust")) + ctrl |= PAM_TRUST_ARG; + else if (!strcmp(*argv,"deny")) + ctrl |= PAM_DENY_ARG; + else if (!strncmp(*argv,"group=",6)) + strcpy(use_group,*argv+6); + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN +int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + int ctrl; + const char *username; + char *fromsu; + struct passwd *pwd, *tpwd; + struct group *grp; + int retval = PAM_AUTH_ERR; + + /* Init the optional group */ + bzero(use_group,sizeof(use_group)); + + ctrl = _pam_parse(argc, argv); + retval = pam_get_user(pamh,&username,NULL); + if ((retval != PAM_SUCCESS) || (!username)) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_DEBUG,"can not get the username"); + return PAM_SERVICE_ERR; + } + + /* su to a uid 0 account ? */ + pwd = getpwnam(username); + if (!pwd) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"unknown user %s",username); + return PAM_USER_UNKNOWN; + } + + /* Now we know that the username exists, pass on to other modules... + * the call to pam_get_user made this obsolete, so is commented out + * + * pam_set_item(pamh,PAM_USER,(const void *)username); + */ + + /* is this user an UID 0 account ? */ + if(pwd->pw_uid) { + /* no need to check for wheel */ + return PAM_IGNORE; + } + + if (ctrl & PAM_USE_UID_ARG) { + tpwd = getpwuid(getuid()); + if (!tpwd) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"who is running me ?!"); + return PAM_SERVICE_ERR; + } + fromsu = tpwd->pw_name; + } else { + fromsu = getlogin(); + if (!fromsu) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"who is running me ?!"); + return PAM_SERVICE_ERR; + } + } + + if (!use_group[0]) { + if ((grp = getgrnam("wheel")) == NULL) { + grp = getgrgid(0); + } + } else + grp = getgrnam(use_group); + + if (!grp || !grp->gr_mem) { + if (ctrl & PAM_DEBUG_ARG) { + if (!use_group[0]) + _pam_log(LOG_NOTICE,"no members in a GID 0 group"); + else + _pam_log(LOG_NOTICE,"no members in '%s' group",use_group); + } + if (ctrl & PAM_DENY_ARG) + /* if this was meant to deny access to the members + * of this group and the group does not exist, allow + * access + */ + return PAM_IGNORE; + else + return PAM_AUTH_ERR; + } + + if (is_on_list(grp->gr_mem, fromsu)) { + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"Access %s to '%s' for '%s'", + (ctrl & PAM_DENY_ARG)?"denied":"granted", + fromsu,username); + if (ctrl & PAM_DENY_ARG) + return PAM_PERM_DENIED; + else + if (ctrl & PAM_TRUST_ARG) + return PAM_SUCCESS; + else + return PAM_IGNORE; + } + + if (ctrl & PAM_DEBUG_ARG) + _pam_log(LOG_NOTICE,"Access %s for '%s' to '%s'", + (ctrl & PAM_DENY_ARG)?"granted":"denied",fromsu,username); + if (ctrl & PAM_DENY_ARG) + return PAM_SUCCESS; + else + return PAM_PERM_DENIED; +} + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +} + + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_wheel_modstruct = { + "pam_wheel", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; + +#endif + +/* + * Copyright (c) Cristian Gafton , 1996, 1997 + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/modules/register_static b/modules/register_static new file mode 100755 index 00000000..2067ac7e --- /dev/null +++ b/modules/register_static @@ -0,0 +1,49 @@ +#!/bin/bash + +if [ `basename $PWD` != "modules" ]; then + echo "$0 must be run from the .../modules directory" + exit 1 +fi + +merge_line () +{ + if [ $# != 3 ]; then + echo "usage: merge_line token filename 'new line'" + fi + if [ -f $2 ]; then +# remove any existing entry... + grep -v "$1" $2 > tmp.$2 + rm -f $2 + mv {tmp.,}$2 + fi + cat << EOT >> $2 +$3 +EOT + +} + + +if [ $# -ne 2 ]; then + + cat << EOT 2>&1 +$0: this script takes TWO arguments: + the 'alphanumeric label' of the module and the location of + its object file from the .../modules/ directory +EOT + exit 1 + +else + echo " + *> registering static module: $1 ($2) <* +" + merge_line "$1" _static_module_list "\ +extern struct pam_module _$1_modstruct;" + + merge_line "$1" _static_module_entry " &_$1_modstruct," + if [ -n "$2" ]; then + merge_line "$2" _static_module_objects "../modules/$2" + fi + +fi + +exit 0 -- cgit v1.2.3