xref: /illumos-gate/usr/src/lib/libsecdb/common/i.rbac (revision 8d0c3d29bb99f6521f2dc5058a7e4debebad7899)
1#!/bin/sh
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22# i.rbac
23#
24# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
25#
26# class action script for "rbac" class files
27# installed by pkgadd
28#
29# Files in "rbac" class:
30#
31# /etc/security/{prof_attr,exec_attr,auth_attr}
32# /etc/user_attr
33#
34#  Allowable exit codes
35#
36# 0 - success
37# 2 - warning or possible error condition. Installation continues. A warning
38#     message is displayed at the time of completion.
39#
40
41umask 022
42
43tmp_dir=${TMPDIR:-/tmp}
44
45PATH="/usr/bin:/usr/sbin:${PATH}"
46export PATH
47
48basename_cmd=basename
49cp_cmd=cp
50egrep_cmd=egrep
51mv_cmd=mv
52nawk_cmd=nawk
53rm_cmd=rm
54sed_cmd=sed
55sort_cmd=sort
56
57# $1 is the type
58# $2 is the "old/existing file"
59# $3 is the "new (to be merged)" file
60# $4 is the output file
61# returns 0 on success
62# returns 2 on failure if nawk fails with non-zero exit status
63#
64dbmerge() {
65#
66# Remove the ident lines.
67#
68	${egrep_cmd} -v '^#[pragma 	]*ident' $2 > $4.old 2>/dev/null
69#
70# If the new file has a Sun copyright, remove the Sun copyright from the old
71# file.
72#
73	newcr=`${egrep_cmd} '^# Copyright.*Sun Microsystems, Inc.' $3 \
74	    2>/dev/null`
75	if [ -n "${newcr}" ]; then
76		$sed_cmd -e '/^# Copyright.*Sun Microsystems, Inc./d' \
77		    -e '/^# All rights reserved./d' \
78		    -e '/^# Use is subject to license terms./d' \
79		    $4.old > $4.$$ 2>/dev/null
80		$mv_cmd $4.$$ $4.old
81	fi
82#
83# If the new file has the CDDL, remove it from the old file.
84#
85	newcr=`${egrep_cmd} '^# CDDL HEADER START' $3 2>/dev/null`
86	if [ -n "${newcr}" ]; then
87		$sed_cmd -e '/^# CDDL HEADER START/,/^# CDDL HEADER END/d' \
88		    $4.old > $4.$$ 2>/dev/null
89		$mv_cmd $4.$$ $4.old
90	fi
91#
92# Remove empty lines and multiple instances of these comments:
93#
94	$sed_cmd -e '/^# \/etc\/security\/exec_attr/d' -e '/^#$/d' \
95		-e '/^# execution attributes for profiles./d' \
96		-e '/^# See exec_attr(4)/d' \
97		-e '/^# \/etc\/user_attr/d' \
98		-e '/^# user attributes. see user_attr(4)/d' \
99		-e '/^# \/etc\/security\/prof_attr/d' \
100		-e '/^# profiles attributes. see prof_attr(4)/d' \
101		-e '/^# See prof_attr(4)/d' \
102		-e '/^# \/etc\/security\/auth_attr/d' \
103		-e '/^# authorizations. see auth_attr(4)/d' \
104		-e '/^# authorization attributes. see auth_attr(4)/d' \
105		    $4.old > $4.$$
106	$mv_cmd $4.$$ $4.old
107#
108# Retain old and new header comments.
109#
110	$sed_cmd -n -e '/^[^#]/,$d' -e '/^##/,$d' -e p $4.old > $4
111	$rm_cmd $4.old
112	$sed_cmd -n -e '/^[^#]/,$d' -e '/^##/,$d' -e p $3 >> $4
113#
114# Handle line continuations (trailing \)
115#
116 	$sed_cmd \
117 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
118 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
119 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
120 	    $2 > $4.old
121 	$sed_cmd \
122 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
123 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
124 	    -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
125 	    $3 > $4.new
126#
127#!/usr/bin/nawk -f
128#
129#       dbmerge type=[auth|prof|user|exec] old-file new-file
130#
131#       Merge two versions of an RBAC database file. The output
132#       consists of the lines from the new-file, while preserving
133#       user customizations in the old-file. Specifically, the
134#       keyword/value section of each record contains the union
135#       of the entries found in both files. The value for each
136#       keyword is the value from the new-file, except for three
137#       keywords ("auths", "profiles", "roles") where the values
138#       from the old and new files are merged.
139#
140#	The output is run through sort except for the comments
141#	which will appear first in the output.
142#
143#
144	$nawk_cmd  '
145
146BEGIN {
147	FS=":"
148}
149
150/^#/ || /^$/ {
151	continue;
152}
153
154{
155	# For each input line, nawk automatically assigns the complete
156	# line to $0 and also splits the line at field separators and
157	# assigns each field to a variable $1..$n.  Assignment to $0
158	# re-splits the line into the field variables.  Conversely,
159	# assgnment to a variable $1..$n will cause $0 to be recomputed
160	# from the field variable values.
161	#
162	# This code adds awareness of escaped field separators by using
163	# a custom function to split the line into a temporary array.
164	# It assigns the empty string to $0 to clear any excess field
165	# variables, and assigns the desired elements of the temporary
166	# array back to the field variables $1..$7.
167	#
168	# Subsequent code must not assign directly to $0 or the fields
169	# will be re-split without regard to escaped field separators.
170	split_escape($0, f, ":");
171	$0 = "";
172	$1 = f[1];
173	$2 = f[2];
174	$3 = f[3];
175	$4 = f[4];
176	$5 = f[5];
177	$6 = f[6];
178	$7 = f[7];
179}
180
181type == "auth" {
182	key = $1 ":" $2 ":" $3 ;
183	if (NR == FNR) {
184		short_comment[key] = $4 ;
185		long_comment[key] = $5;
186		record[key] = $6;
187	}
188	else {
189		if ( $4 != "" ) {
190			short_comment[key] = $4 ;
191		}
192		if ( $5 != "" ) {
193			long_comment[key] =  $5 ;
194		}
195		print key ":" short_comment[key] ":" long_comment[key] ":" \
196		    merge_attrs(record[key], $6);
197		delete record[key];
198	}
199}
200
201type == "prof" {
202	key = $1 ":" $2 ":" $3 ;
203	if (NR == FNR) {
204		comment[key] = $4;
205		record[key] = $5;
206	}
207	else {
208		if ( $4 != "" ) {
209			comment[key] = $4 ;
210		}
211		if (key != "::") {
212			print key ":" comment[key] ":" \
213			    merge_attrs(record[key], $5);
214		}
215		delete record[key];
216	}
217}
218
219type == "exec" {
220	key = $1 ":" $2 ":" $3 ":" $4 ":" $5 ":" $6 ;
221	# Substitute new entries, do not merge.
222	record[key] = $7;
223}
224
225type == "user" {
226	key = $1 ":" $2 ":" $3 ":" $4 ;
227	if (NR == FNR)
228		record[key] = $5;
229	else {
230		print key ":" merge_attrs(record[key], $5);
231		delete record[key];
232	}
233}
234
235END {
236	for (key in record) {
237		if (type == "prof") {
238			if (key != "::") {
239				print key ":" comment[key] ":" record[key];
240			}
241		} else
242			if (type == "auth") {
243				print key ":" short_comment[key] ":"  \
244				    long_comment[key] ":" record[key];
245			} else
246				print key ":" record[key];
247		}
248}
249
250function merge_attrs(old, new, cnt, new_cnt, i, j, list, new_list, keyword)
251{
252	cnt = split_escape(old, list, ";");
253	new_cnt = split_escape(new, new_list, ";");
254	for (i = 1; i <= new_cnt; i++) {
255		keyword = substr(new_list[i], 1, index(new_list[i], "=")-1);
256		for (j = 1; j <= cnt; j++) {
257			if (match(list[j], "^" keyword "=")) {
258				list[j] = merge_values(keyword, list[j],
259				    new_list[i]);
260				break;
261			}
262		}
263		if (j > cnt)
264			list[++cnt] = new_list[i];
265	}
266
267	return unsplit(list, cnt, ";"); \
268}
269
270function merge_values(keyword, old, new, cnt, new_cnt, i, j, list, new_list, d)
271{
272	if (keyword != "auths" && keyword != "profiles")
273		return new;
274
275	cnt = split(substr(old, length(keyword)+2), list, ",");
276	new_cnt = split(substr(new, length(keyword)+2), new_list, ",");
277
278	# If the existing list contains "All", remove it and add it
279	# to the new list; that way "All" will appear at the only valid
280	# location, the end of the list.
281	if (keyword == "profiles") {
282		d = 0;
283		for (i = 1; i <= cnt; i++) {
284			if (list[i] != "All")
285				list[++d] = list[i];
286		}
287		if (cnt != d) {
288			new_list[++new_cnt] = "All";
289			cnt = d;
290		}
291	}
292	for (i = 1; i <= new_cnt; i++) {
293		for (j = 1; j <= cnt; j++) {
294			if (list[j] == new_list[i])
295				break;
296		}
297		if (j > cnt)
298			list[++cnt] = new_list[i];
299	}
300
301	return keyword "=" unsplit(list, cnt, ",");
302}
303
304# This function is similar to the nawk built-in split() function,
305# except that a "\" character may be used to escape any subsequent
306# character, so that the escaped character will not be treated as a
307# field separator or as part of a field separator regular expression.
308# The "\" characters will remain in the elements of the output array
309# variable upon completion.
310function split_escape(str, list, fs, cnt, saved, sep)
311{
312	# default to global FS
313	if (fs == "")
314		fs = FS;
315	# initialize empty list, cnt, saved
316	split("", list, " ");
317	cnt = 0;
318	saved = "";
319	# track whether last token was a field separator
320	sep = 0;
321	# nonzero str length indicates more string left to scan
322	while (length(str)) {
323		if (match(str, fs) == 1) {
324			# field separator, terminates current field
325			list[++cnt] = saved;
326			saved = "";
327			str = substr(str, RLENGTH + 1);
328			sep = 1;
329		} else if (substr(str, 1, 1) == "\\") {
330			# escaped character
331			saved = saved substr(str, 1, 2);
332			str = substr(str, 3);
333			sep = 0;
334		} else {
335			# regular character
336			saved = saved substr(str, 1, 1);
337			str = substr(str, 2);
338			sep = 0;
339		}
340	}
341	# if required, append final field to list
342	if (sep || length(saved))
343		list[++cnt] = saved;
344
345	return cnt;
346}
347
348function unsplit(list, cnt, delim, str)
349{
350	str = list[1];
351	for (i = 2; i <= cnt; i++)
352		str = str delim list[i];
353	return str;
354}' \
355	type=$1 $4.old $4.new > $4.unsorted
356	rc=$?
357	$sort_cmd < $4.unsorted >> $4
358	return $rc
359}
360
361# $1 is the merged file
362# $2 is the target file
363#
364commit() {
365	# Make sure that the last mv uses rename(2) by first moving to
366	# the same filesystem.
367	$mv_cmd $1 $2.$$
368	$mv_cmd $2.$$ $2
369	return $?
370}
371
372outfile=""
373type=""
374set_type_and_outfile() {
375	#
376	# Assumes basename $1 returns one of
377	# prof_attr, exec_attr, auth_attr, or user_attr
378	#
379	fname=`$basename_cmd $1`
380	type=`echo $fname | $sed_cmd -e s'/^\([a-z][a-z]*\)_attr$/\1/' `
381	case "$type" in
382		"prof"|"exec"|"user"|"auth") ;;
383		*) return 2 ;;
384	esac
385
386	outfile=$tmp_dir/rbac_${PKGINST}_${fname}_merge.$$
387
388	return 0
389}
390
391cleanup() {
392	$rm_cmd -f $outfile $outfile.old $outfile.new $outfile.unsorted
393
394	return 0
395}
396
397exit_status=0
398
399# main
400
401while read newfile oldfile ; do
402	if [ -n "$PKGINST" ]
403	then
404		# Install the file in the "fragment" directory.
405		mkdir -m 755 -p ${oldfile}.d
406		rm -f ${oldfile}.d/"$PKGINST"
407		cp $newfile ${oldfile}.d/"$PKGINST"
408
409		# Make sure that it is marked read-only.
410		chmod a-w,a+r ${oldfile}.d/"$PKGINST"
411
412		# We also execute the rest of the i.rbac script.
413	fi
414
415	if [ ! -f $oldfile ]; then
416		cp $newfile $oldfile
417	else
418		set_type_and_outfile $newfile ||
419			set_type_and_outfile $oldfile
420		if [ $? -ne 0 ]; then
421			echo "$0 : $newfile not one of" \
422			    " prof_attr, exec_attr, auth_attr, user_attr"
423			exit_status=2
424			continue
425		fi
426
427		dbmerge $type $oldfile $newfile $outfile
428		if [ $? -ne 0 ]; then
429			echo "$0 : failed to merge $newfile with $oldfile"
430			cleanup
431			exit_status=2
432			continue
433		fi
434
435		commit $outfile $oldfile
436		if [ $? -ne 0 ]; then
437			echo "$0 : failed to mv $outfile to $2"
438			cleanup
439			exit_status=2
440			continue
441		fi
442
443		cleanup
444	fi
445done
446
447if [ "$1" = "ENDOFCLASS" ]; then
448	exit 0
449fi
450
451exit $exit_status
452