File: bastille-tmpdir.sh

package info (click to toggle)
bastille 1:1.3.0-2.1
  • links: PTS
  • area: main
  • in suites: woody
  • size: 1,576 kB
  • ctags: 397
  • sloc: perl: 8,407; sh: 1,879; ansic: 638; makefile: 203; csh: 17
file content (369 lines) | stat: -rw-r--r-- 13,386 bytes parent folder | download
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#!/bin/sh
#
# bastille-tmpdir.sh
#
# version 1.14
#
# This script sets TMPDIR and TMP environment variables for some added
# safety on multi-user systems. Many applications write temporary
# files in unsafe ways to /tmp unless TMPDIR and/or TMP are set.
#
# For performance, backup, and housekeeping reasons, this script
# tries to (safely!) use space in /tmp (or other directory: see Settings, below)
#  performance:  many systems use tmpfs for /tmp
#  backup:       many backup configurations ignore /tmp
#  housekeeping: many systems prune /tmp with tools like 'tmpwatch'
#
# This script also makes two levels of directories so other users cannot
# glean much information by stat()ing the parent of $TMPDIR
#
#  -------------    ****   Important: required apps   ****    -------------
#
# 	You should have the following apps installed, and in a normal search path,
#	for this script to work properly:
#		grep, sed, which, uname, cut, ls, grep, mkdir, echo, cat, awk, id, expr, date, eval
#
#	The following apps are recommended:
#		printf, od
#	Also a readable /dev/urandom device is recommended.
#
#  -------------    ****   Important: required apps   ****    -------------
#
# Copyright (c) 2000-2001 Peter Watkins
# with thanks to Sweth
#
# licensed under the terms of the GNU General Public License
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# ---- Settings ----------------------------------------------------
#
# PREFDIR: the directory where TMPDIR should be made
#          /tmp is usually the best choice, unless you have another
#          partition explicitly created for temporary files
#           ** $PREFDIR should be user-writable, root-owned, and "sticky" (mode 01777) **
#	    ** and all parent dirs must be root-owned and sticky or mode go-w **
PREFDIR=/tmp
# 
# MAXTRIES: how many times to try creating a directory in $PREFDIR
#           before giving up and using $HOME. Since $PREFDIR is essentially world-writable,
#           we may have to try multiple times before successfully making
#           a safe temporary directory
MAXTRIES=20
#
# FAKEDATE: if non-empty, this will be used with 'touch' to set a fake timestamp
#	    on the top-level TMPDIR to further limit the information other users
#	    might glean from looking for tmpdir info (though users gathering historical
#	    info can determine the true age of the directory). Read the man page
#	    for 'touch' for format information
FAKEDATE=200001011200		# set top dir to timestamp of noon, 1 Jan 2000
#
# STATEFUL: if set to "YES", will keep trck of TMPDIRs created on different hosts in
#           $HOME/.tmpdirs/ so that $PREFDIR won't be littered with directories,
#           and nosy neighbors will have less information about system usage
STATEFUL="YES"
#
# RECREATE_DIR: set to "YES" if you want the script to try and recreate $TMPDIR
#	        if the user had a value stored in their $HOME state file but the
#               directory no longer exists. Advantage: nosy neighbors less likely to
#               notice new directories. Disadvantage: regular users may become accustomed
#               to having the same $TMPDIR each time, may be foolish enough to script
#               around the value of $TMPDIR instead of using the TMPDIR env variable.
RECREATE_DIR="NO"
#
# WHICH:	Please tell me where the real 'which' is (some Linux
#		distributions set up an alias for which!)
#WHICH=/usr/bin/which		# manual
# or the following will look for which:
for p in /usr/sbin /sbin /usr/bin /bin ; do
	[ -x "${p}/which" ] && WHICH="${p}/which"
done
#
# DEFENSE_SCRIPT: location of script to defend $TMPDIR against programs like 'tmpwatch'
#		  This script typically 1) does something inside $TMPDIR (mkdir/rmdir)
#		  to keep $TMPDIR from looking "stale" and 2) has code to notify the
#		  user (write/mail/X pop-up) if something seems amiss 
DEFENSE_SCRIPT=/etc/bastille-tmpdir-defense.sh
#
# ---- Functions --------------------------------------------------
#
app_available() {
	# takes a single argument, an app name, and tests for its availabilty
	${WHICH} $1 >/dev/null 2>&1
	return $?
}
#
get_random() {
	# generate a string of random characters
	r=1
	[ ! -r /dev/urandom ] && r=0
	for a in printf od cut; do
		app_available $a || r=0
	done 
	if [ $r -eq 1 ]; then
		RAND=`printf %x $(od -N 8 /dev/urandom) | cut -c -16`
	else
		# less random, but we're careful with this anyway...
		RAND="${RANDOM}"
		# Solaris /bin/sh doesn't support $RANDOM !?!
		if [ "${RAND}" = "" ]; then
			if [ -x /bin/ksh ]; then
				# ask the Korn shell for a random number
				RAND=`echo 'echo $RANDOM' |/bin/ksh`
			else
				# use our PID 
				RAND=$$
			fi
		fi
	fi
	unset r
	return 0
}
#
safe_hostname() {
	# return `uname -n` scrubbed of unexpected chars
	MYHOST=`uname -n 2>/dev/null| sed 's:[^a-zA-Z0-9\.\-]::g; s:\.\.::g' 2>/dev/null| cut -c1-64 2>/dev/null`
	[ -z "${MYHOST}" ] && MYHOST=DEFAULT
	return 0
}
#
dir_owned_by() {
	# check if $1 is a directory owned by $2
	# the 'cut' here looks extraneous, but some versions of 'awk' (e.g. SunOS 5.x)
	# choke on long lines, so 'cut' is used here for some added safety
	owner=`ls -ld $1 2> /dev/null | cut -c1-40 | awk '{print $3}'`
	[ "${owner}" != "" -a "${owner}" = "$2" ] && return 0
	return 1
}
#
dir_has_perms() {
	# check if $1 has perms $2 (=="0700" or "sticky" or "go-w")
	if [ "$2" = "0700" ]; then
		# this is an exact perm test (but we allow drw[xs]--[S-]--[T-], too)
		[ ! -z "$(/bin/ls -ld $1 2> /dev/null | grep '^drw[xs]\-\-[S\-]\-\-[T\-]')" ] && return 0
	fi
	if [ "$2" = "sticky" ]; then
		# only care about dir flag and sticky flag
		[ ! -z "$(ls -ld $1 2> /dev/null | grep '^d........t')" ] && return 0
	fi
	if [ "$2" = "go-w" ]; then
		# only care about dir flag and g/o write flags
		[ ! -z "$(ls -ld $1 2> /dev/null | grep '^d....-..-.')" ] && return 0
	fi
	return 1
}
#
# ---- Main script ------------------------------------------------
#
if [ -z "${TMPDIR}" -o \! -d "${TMPDIR}" ]; then
    # we only go through this if we don't already have $TMPDIR
    # or if the directory no longer exists
    ${WHICH} cp >/dev/null 2>&1
    if [ $? -ne 0 -o ! -x "${WHICH}" ]; then
       echo "WARNING: 'which' not available; unable to set safe TMPDIR"
	[ $# -gt 0 ] && exit 1	# non-zero exit code for csh
    else
        RC=0
        TRIES=0
	safe_hostname		# set MYHOST
	get_random		# set RAND
	TDIR=${HOME:-}/.tmpdirs
	[ $TDIR = "//.tmpdirs" ] && TDIR="/.tmpdirs"    # for ~root = /
	[ "${HOME:-}" = "" ] && STATEFUL="NO"           # no state if no $HOME
	TRYDIR=""
	TRYDIR2=""
	TESTDIR=""
	TESTDIR2=""
	THIS_USER=`id -un`
	#
	# make the dir to store info about reusable TMPDIRS
	if [ ! -d "$TDIR" -a "${STATEFUL}" = "YES" ]; then
		mkdir -m 0700 "$TDIR" || TDIR=""
	fi
	# do we have a suggested TMPDIR inside a safe-looking $TDIR?
	w=0
	[ \( ! -z "${TDIR}" \) -a -f "${TDIR}/${MYHOST}" -a "${STATEFUL}" = "YES" ] && dir_owned_by "$TDIR" $THIS_USER && dir_has_perms "$TDIR" "0700" && w=1
	if [ $w -eq 1 ]; then
		# FIXME: a more paranoid script might check this data more carefully
		# but since TDIR is in $HOME, any tampering  suggests
		# bigger problems, so I'm skipping those tests for now
		# FIXME: it would also be more efficient, disk-wise, to use a single
		# state file inside $HOME instead of using a separate file
		# for each `uname -n` hostname
		TRYDIR=`cat "${TDIR}/${MYHOST}"`
		x=0
		app_available sed && app_available grep && x=1
		if [ $x -eq 1 ]; then
			# minimal sanity checks: no "..", stay in $PREFDIR
			# this will probably reject any suggestions inside 
			# $HOME, which is probably desirable!
			TRYDIR=`cat "${TDIR}/${MYHOST}" | sed 's:\.\.::g' | grep "^${PREFDIR}/"`
		fi
		unset x
		TRYDIR2="${TRYDIR}"
	fi
	unset w
	if [ ! -z "${TRYDIR}" ]; then
		# see if $TRYDIR exists, is owned by me, and looks safe
		#echo "FIXME: add code here!"
		TESTDIR=`echo "${TRYDIR}" | sed 's:/[^\/]*$:: ; s:^$:/:' `
		# TMPDIR must be owned by me, mode 0700
		x=0
		dir_owned_by "${TRYDIR}" ${THIS_USER} && dir_has_perms "${TRYDIR}" "0700" || x=1
		if [ $x -eq 1 ]; then
			# bad permissions on TMPDIR, pick another
			TRYDIR=""
		fi
		unset x
		# same thing with TMPDIR's parent
		if [ ! -z "${TRYDIR}" ]; then
			x=0
			dir_owned_by "${TESTDIR}" ${THIS_USER} && dir_has_perms "${TESTDIR}" "0700" || x=1
			if [ $x -eq 1 ]; then
				# bad permissions on TMPDIR, pick another
				TRYDIR=""
			fi
			unset x
		fi 
		# higher level dirs must be root/sticky or root/go-w
		TESTDIR2="${TESTDIR}"
		TESTDIR=`echo "${TESTDIR}" | sed 's:/[^\/]*$:: ; s:^$:/:' `
		while [ "${TESTDIR}" != "${TESTDIR2}" -a \( ! -z "${TRYDIR}" \) ]; do
			dir_owned_by "${TESTDIR}" "root" ; x=$?
			dir_has_perms "${TESTDIR}" "sticky" ; y=$?
			dir_has_perms "${TESTDIR}" "go-w" ; z=$?
			if [ \( \( $z -ne 0 \) -a \( \( $y -ne 0 \) \) -o \( $x -ne 0 \) \) ]; then
				# bad perms
				TRYDIR=""
				TESTDIR="${TESTDIR2}"
			fi
			unset x y z
			if [ ! -z "${TRYDIR}" ]; then
				TESTDIR2="${TESTDIR}"
				TESTDIR=`echo "${TESTDIR}" | sed 's:/[^\/]*$:: ; s:^$:/:' `
			fi	
		done
	fi
	if [ -z "${TRYDIR}" ]; then
		# need to (re)create a safe directory
		#
		RC=1
		# first try to remake the old dir name, if possible, and so configured
		x=0
		app_available sed && [ ! -z "${TRYDIR2}" -a "${RECREATE_DIR}" = "YES" ] && x=1
		if [ $x -eq 1 ]; then
			# strip the lower level dir name ("/files") from TRYDIR2
			TRYDIR=`echo ${TRYDIR2} | sed 's:/files$::'`
			(mkdir -m 0700 "${TRYDIR}" && mkdir -m 0700 "${TRYDIR}/files") 2>/dev/null
			RC=$?
			[ $RC -eq 0 -a ! -z "${FAKEDATE}" ] && touch -t $FAKEDATE "${TRYDIR}" 2>/dev/null
		fi
		unset x
	        # see if we need to make a dir, and we haven't already tried too hard
        	while [ \( $RC -ne 0 \) -a \( $TRIES -lt $MAXTRIES \) ]; do
			TRYDIR="${PREFDIR}/${THIS_USER:-}-tmp.${RAND}"
			(mkdir -m 0700 "${TRYDIR}" && mkdir -m 0700 "${TRYDIR}/files") 2>/dev/null
			RC=$?
			[ $RC -eq 0 -a ! -z "${FAKEDATE}" ] && touch -t $FAKEDATE "${TRYDIR}" 2>/dev/null
			TRIES=`expr $TRIES + 1`
			get_random		# set RAND
		done
		if [ $TRIES -ge $MAXTRIES ]; then
			# fallback is to use space in $HOME
			echo "Warning: Unable to make safe temp dir in ${PREFDIR}"
			TRYDIR="${HOME-}/tmp-${RAND}"
			(mkdir -m 0700 "${TRYDIR}" && mkdir -m 0700 "${TRYDIR}/files") 2> /dev/null   
			RC=$?
			# don't bother to 'touch' the dir if inside $HOME
			# don't record state if the dir is inside $HOME
			[ \( ! -z "${TDIR}" \) -a \( -d "${TDIR}" \) -a \( ! -z "${MYHOST}" \) -a "${STATEFUL}" = "YES" -a $RC -eq 0 ] && dir_owned_by "${TDIR}" $THIS_USER && dir_has_perms "${TDIR}" 0700 && rm "${TDIR}/${MYHOST}" 2>/dev/null && STATEFUL="NO"
		fi
		if [ $RC -eq 0 ]; then 
			# the actual temp dir is $TRYDIR/files
			TRYDIR="${TRYDIR}/files"
		fi
	fi
        if [ $RC -ne 0 ]; then
                echo "ERROR: Unable to make safe temp directory!"
		echo "       Proceed with caution!"
		[ $# -gt 0 ] && exit 1	# non-zero exit code for csh
        else
                # success
                TMP="${TRYDIR}"
                TMPDIR="${TRYDIR}"
                export TMP
                export TMPDIR
		x=0
		[ \( ! -z "${TDIR}" \) -a \( -d "${TDIR}" \) -a \( ! -z "${MYHOST}" \) -a "${STATEFUL}" = "YES" ] && dir_owned_by "${TDIR}" $THIS_USER && dir_has_perms "${TDIR}" 0700 && x=1
		if [ $x -eq 1 ]; then
			# record the TMPDIR value for the next time
			echo "${TMPDIR}" > "${TDIR}/${MYHOST}"
		fi
		unset x
		# Below: can't run the bg task if spawned by the csh
		# wrapper, thus the insistence that $# -eq 0
		if [ $# -eq 0 -a \! -z "${DEFENSE_SCRIPT}" -a -f "${DEFENSE_SCRIPT}" -a -x "${DEFENSE_SCRIPT}" ]; then
			# schedule background task to update ctime/mtime
			# attributes so $TMPDIR won't be purged while the
			# user remains logged in
			# Why eval? It's stupid, but if you don't 'eval' this, 
			#  it looks awkward in 'jobs' (e.g. "/bin/sh $DEFENSE_SCRIPT &"
			#  instead of "/bin/sh /etc/bastille-tmpdir-defense.sh &"
			eval "$DEFENSE_SCRIPT $$ &"
		fi
        fi
	# unset variables used in this loop
	unset MYHOST
	unset RC
	unset TDIR
	unset THIS_USER
	unset TESTDIR
	unset TESTDIR2
	unset TRIES
	unset TRYDIR
	unset TRYDIR2
    fi
else
	x=0
	[ $# -gt 0 ] && x=1
	if [ $x -eq 1 ]; then
		echo ""
		unset x
		exit 0
	fi
fi
# unset global variables
unset DEFENSE_SCRIPT
unset FAKEDATE
unset MAXTRIES
unset PREFDIR
unset RECREATE_DIR
unset TOUCH_ALL_DIRS
unset TOUCH_INTERVAL
unset WHICH

# now undefine the functions
unset app_available
unset get_random
unset safe_hostname
unset dir_owned_by
unset dir_has_perms

# If we're passed an argument, echo the TMPDIR value
# This allows /bin/csh code like this (but safer!)
#    if ( -f /etc/profile.d/bastille-tmpdir.sh ) then
#	# actually we'd set an unimportant var, and watch $?
#	# before setting TMPDIR/TMP
#	setenv TMPDIR `/bin/sh /etc/profile.d/bastille-tmpdir.sh echo`
#	setenv TMP $TMPDIR
#    endif
# so we don't have to re-write this code for other login shells ;-)
#
[ $# -gt 0 ] && echo $TMPDIR