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