Skip to content

Commit 809c029

Browse files
committed
Initial import
0 parents  commit 809c029

File tree

7 files changed

+467
-0
lines changed

7 files changed

+467
-0
lines changed

AUTHORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The primary author of pwfilter is Alex Waite <[email protected]>. See the
2+
commit log for additional author information.

LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2015, Alex Waite
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the copyright holder nor the names of its contributors
15+
may be used to endorse or promote products derived from this software
16+
without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

PORTABILITY

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
pwfilter is written assuming a POSIX compliant Bourne shell with additional
2+
support for the keyword `local` for variable scoping. It also requires the
3+
POSIX utility `cat` and the non-POSIX /dev/stdin device file. Nearly all *nixes
4+
should have at least one shell that can run pwfilter.
5+
6+
== Shells Known To Work
7+
- sh (some modern variants. e.g. FreeBSD's sh)
8+
- ash (including busybox's)
9+
- bash
10+
- dash
11+
- ksh88
12+
- lksh
13+
- mksh
14+
- pdksh
15+
- posh
16+
- zsh
17+
18+
== Bourne Shells Known To Not Work
19+
- sh (the original AT&T sh)
20+
- does not have support for the `local` keyword
21+
- ksh93
22+
- does not have support for the `local` keyword. It /does/ support `typeset`,
23+
but typeset does not scope variables locally unless functions are written as
24+
"function foo { }" rather than "foo() { }". The former syntax is non-POSIX
25+
and causes dash (and I assume other shells too) to choke. Thus, adjusting
26+
the function declaration and a simple "alias local=typeset" will not work.
27+
28+
== Shells Which Will Never Be Supported
29+
This should go without saying, but given that pwfilter is written using Bourne
30+
shell syntax, it will never be able to run in non-Bourne shells. This includes
31+
the C shell family (csh, tcsh) and any "exotic" shells like fish.
32+
33+
== Operating System Notes
34+
Not sure which shell to use? Chances are good on a modern system that the
35+
default (/bin/sh) will point to a sane and compatible shell.
36+
37+
*BSD and Linux
38+
- the default /bin/sh nearly always works
39+
- dash is quite fast; if you have a choice, use it
40+
- otherwise, there are many others available via the package manager
41+
Solaris 10:
42+
- /bin/ksh (which is ksh88) and /bin/bash will work
43+
Solaris 11:
44+
- /bin/bash (or ksh88 if installed, though it isn't available by default)
45+
Other SunOS (Illumos, OmniOS, etc)
46+
- /bin/bash or many others available via the package manager
47+
OS X
48+
- /bin/sh points to bash
49+
HP-UX:
50+
- does not provide /dev/stdin. Either create a named-pipe at that location
51+
or avoid piping input into pwfilter.
52+
53+
== Man Page
54+
pwfilter's man page is written using mdoc (which is largely based off of roff).
55+
Most modern unices, with the exception of Solaris, have support to correctly
56+
display mdoc man pages.

grpfilter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pwfilter

pwfilter

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/bin/sh
2+
#
3+
# This file is licensed under the BSD-3-Clause license.
4+
# See the AUTHORS and LICENSE files for more information.
5+
6+
############
7+
## Variables
8+
############
9+
readonly VERSION=0.3
10+
readonly SCRIPT_NAME=${0##*/}
11+
12+
# Overriding IFS is the only way to handle spaces in the files without resorting
13+
# to spawning subshells :-/
14+
readonly NEWLINE='
15+
'
16+
readonly ORIG_IFS=$IFS
17+
IFS=$NEWLINE
18+
19+
INCLUDE_RANGE=''
20+
INCLUDE=''
21+
EXCLUDE=''
22+
EXCLUDE_RANGE=''
23+
24+
readonly GROUP_FILE='/etc/group'
25+
readonly PASSWD_FILE='/etc/passwd'
26+
readonly SHADOW_FILE='/etc/shadow'
27+
FILE_TO_PARSE=''
28+
FILE_CONTENTS=''
29+
SHADOW_TO_PARSE=''
30+
SHADOW_CONTENTS=''
31+
32+
############
33+
## FUNCTIONS
34+
############
35+
36+
Help() {
37+
cat << EOF
38+
$SCRIPT_NAME v${VERSION}
39+
40+
Syntax:
41+
$SCRIPT_NAME [-h] [-V] |
42+
[-e ID ...] [-E ID ID] [-f filename [filename]] [-i ID ...]
43+
[-I ID ID] ...
44+
45+
OPTIONS:
46+
-e, --exclude ID ... = Exclude these specific ID(s)
47+
-E, --exclude-range ID ID = Exclude this range of IDs
48+
-f, --file filename [filename] = File to parse rather than system default.
49+
shdwfilter requires both passwd and shadow.
50+
-h, --help = Print this help and exit
51+
-i, --include ID ... = Include these Specific ID(s)
52+
-I, --range ID ID = Include this range of IDs
53+
-V, --version = Print the version number and exit
54+
55+
EOF
56+
}
57+
58+
# return 0 if a desired ID, otherwise 1
59+
# - individuals always win over ranges
60+
# - when in conflict, exclude wins over include
61+
# - ranges are inclusive
62+
DesiredID() {
63+
local uid=$1
64+
# TODO: check if int?
65+
66+
# specified IDs
67+
local i
68+
for i in $EXCLUDE; do # exclude
69+
[ $uid -eq $i ] && return 1
70+
done
71+
for i in $INCLUDE; do # include
72+
[ $uid -eq $i ] && return 0
73+
done
74+
75+
# ranges
76+
for i in $EXCLUDE_RANGE; do # exclude
77+
[ $uid -ge ${i%-*} ] && [ $uid -le ${i#*-} ] && return 1
78+
done
79+
for i in $INCLUDE_RANGE; do # include
80+
[ $uid -ge ${i%-*} ] && [ $uid -le ${i#*-} ] && return 0
81+
done
82+
83+
# if nothing matches, reject
84+
return 1
85+
}
86+
87+
# print message to stderr and exit 1
88+
Fatal() {
89+
printf '%s\n' "$*" >&2
90+
exit 1
91+
}
92+
93+
# return 0 if argument is an integer; 1 if not
94+
IsInt() {
95+
[ -z "${1##*[!0-9]*}" ] && return 1 || return 0
96+
}
97+
98+
#######
99+
## MAIN
100+
#######
101+
102+
# functionality changes depending on how the script is called (typically via symlinks)
103+
case "$SCRIPT_NAME" in
104+
grpfilter*)
105+
FILE_TO_PARSE=$GROUP_FILE
106+
;;
107+
pwfilter*)
108+
FILE_TO_PARSE=$PASSWD_FILE
109+
;;
110+
shdwfilter*)
111+
FILE_TO_PARSE=$PASSWD_FILE
112+
SHADOW_TO_PARSE=$SHADOW_FILE
113+
;;
114+
*)
115+
Fatal "'$SCRIPT_NAME' is an unsupported script name for the pwfilter suite."
116+
;;
117+
esac
118+
119+
# help out if there are no arguments
120+
[ -n "$1" ] || { Help; exit 1; }
121+
122+
# parse options and arguments
123+
while [ -n "$1" ]; do
124+
case "$1" in
125+
'-h'|'--help'|'')
126+
Help
127+
exit 0
128+
;;
129+
'-V'|'--version')
130+
printf '%s v%s\n' "$SCRIPT_NAME" "$VERSION"
131+
exit 0
132+
;;
133+
'-I'|'--range'|'-E'|'--exclude-range')
134+
[ -n "$2" ] && [ -n "$3" ] || Fatal "'$1' requires two arguments."
135+
IsInt "$2" && IsInt "$3" || Fatal "Both '$2' and '$3' must be integers."
136+
[ $2 -le $3 ] || Fatal "'$2' must be <= '$3'."
137+
138+
if [ "$1" = '--range' ] || [ "$1" = '-I' ] ; then # include range
139+
INCLUDE_RANGE="${INCLUDE_RANGE:+${INCLUDE_RANGE}${NEWLINE}}${2}-${3}"
140+
else # exclude range
141+
EXCLUDE_RANGE="${EXCLUDE_RANGE:+${EXCLUDE_RANGE}${NEWLINE}}${2}-${3}"
142+
fi
143+
shift 2
144+
;;
145+
'-i'|'--include'|[!-]*) # specific IDs to include
146+
while IsInt "$1"; do
147+
INCLUDE="${INCLUDE:+${INCLUDE}${NEWLINE}}$1"
148+
shift 1
149+
done
150+
;;
151+
'-e'|'--exclude') # specific IDs to exclude
152+
[ -n "$2" ] || Fatal "'$1' requires an argument."
153+
while IsInt "$2"; do
154+
EXCLUDE="${EXCLUDE:+${EXCLUDE}${NEWLINE}}$2"
155+
shift 1
156+
done
157+
;;
158+
'-f'|'--file') # file to parse
159+
[ -n "$2" ] || Fatal "'$1' requires an argument."
160+
FILE_TO_PARSE=$2 && shift 1
161+
162+
# shdwfilter needs both passwd and shadow files
163+
if [ -z "${SCRIPT_NAME##shdwfilter*}" ]; then
164+
[ -n "$2" ] || Fatal "'$1' requires both passwd and shadow files."
165+
SHADOW_TO_PARSE=$2 && shift 1
166+
fi
167+
;;
168+
-*)
169+
Fatal "'$1' is not a valid '$SCRIPT_NAME' option.";;
170+
esac
171+
[ -n "$1" ] && shift 1 # only shift if there's anything left to shift
172+
done
173+
174+
#
175+
# populate *_CONTENTS, either from piped input or file(s)
176+
#
177+
# TODO: if -f was set, no piped input should be parsed
178+
if [ -p /dev/stdin ]; then # piped input
179+
[ -z "${SCRIPT_NAME##shdwfilter*}" ] && Fatal "'shdwfilter' cannot handle piped input."
180+
while read P_LINE; do
181+
FILE_CONTENTS="${FILE_CONTENTS:+${FILE_CONTENTS}${NEWLINE}}${P_LINE}"
182+
done
183+
else # read in the appropriate file(s)
184+
[ -r "$FILE_TO_PARSE" ] || Fatal "'$FILE_TO_PARSE' is not readable!"
185+
FILE_CONTENTS=`cat "$FILE_TO_PARSE"` || Fatal "Failed to 'cat' file: '$FILE_TO_PARSE'"
186+
187+
if [ -z "${SCRIPT_NAME##shdwfilter*}" ]; then
188+
[ -r "$SHADOW_TO_PARSE" ] || Fatal "'$SHADOW_TO_PARSE' is not readable!"
189+
SHADOW_CONTENTS=`cat "$SHADOW_TO_PARSE"` || Fatal "Failed to 'cat' shadow file: '$SHADOW_TO_PARSE'"
190+
fi
191+
fi
192+
193+
#
194+
# loop over and parse *_CONTENTS;
195+
# print desirable IDs
196+
#
197+
for F_LINE in $FILE_CONTENTS; do
198+
# get the third item (UID/GID) in the line
199+
first_three=${F_LINE#*:*:}
200+
the_id=${first_three%%:*}
201+
202+
IsInt "$the_id" || Fatal "'$the_id' is not a valid id number."
203+
DesiredID "$the_id" || continue
204+
205+
if [ -z "${SCRIPT_NAME##shdwfilter*}" ]; then
206+
user_name=${F_LINE%%:*} # first item (user name) in the passwd line
207+
for S_LINE in $SHADOW_CONTENTS; do
208+
[ "$user_name" = ${S_LINE%%:*} ] && printf '%s\n' "$S_LINE" && break
209+
# believe it or not, the above is actually ~1.4x faster than:
210+
# [ -z "${S_LINE##${user_name}:*}" ]
211+
done
212+
# TODO: If no match, error?
213+
else # password- or group-filter
214+
printf '%s\n' "$F_LINE"
215+
fi
216+
done
217+
218+
# restore original IFS
219+
IFS=$ORIG_IFS

0 commit comments

Comments
 (0)