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 
  41 umask 022
  42 
  43 tmp_dir=${TMPDIR:-/tmp}
  44 
  45 PATH="/usr/bin:/usr/sbin:${PATH}"
  46 export PATH
  47 
  48 basename_cmd=basename
  49 cp_cmd=cp
  50 egrep_cmd=egrep
  51 mv_cmd=mv
  52 nawk_cmd=nawk
  53 rm_cmd=rm
  54 sed_cmd=sed
  55 sort_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 #
  64 dbmerge() {
  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 an Oracle copyright, remove both the Sun and Oracle
  84 # copyrights from the old file.
  85 #
  86         oracle_cr=`${egrep_cmd} '^# Copyright.*Oracle and/or its affiliates.' \
  87             $3 2>/dev/null`
  88         if [ -n "${oracle_cr}" ]; then
  89                 $sed_cmd -e '/^# Copyright.*Sun Microsystems, Inc./d' \
  90                     -e '/^# All rights reserved./d' \
  91                     -e '/^# Use is subject to license terms./d' \
  92                     -e '/^# Copyright.*Oracle and\/or its affiliates./d' \
  93                     $4.old > $4.$$ 2>/dev/null
  94                 $mv_cmd $4.$$ $4.old
  95         fi
  96 #
  97 # If the new file has the CDDL, remove it from the old file.
  98 #
  99         newcr=`${egrep_cmd} '^# CDDL HEADER START' $3 2>/dev/null`
 100         if [ -n "${newcr}" ]; then
 101                 $sed_cmd -e '/^# CDDL HEADER START/,/^# CDDL HEADER END/d' \
 102                     $4.old > $4.$$ 2>/dev/null
 103                 $mv_cmd $4.$$ $4.old
 104         fi
 105 #
 106 # Remove empty lines and multiple instances of these comments:
 107 #
 108         $sed_cmd -e '/^# \/etc\/security\/exec_attr/d' -e '/^#$/d' \
 109                 -e '/^# execution attributes for profiles./d' \
 110                 -e '/^# See exec_attr(4)/d' \
 111                 -e '/^# \/etc\/user_attr/d' \
 112                 -e '/^# user attributes. see user_attr(4)/d' \
 113                 -e '/^# \/etc\/security\/prof_attr/d' \
 114                 -e '/^# profiles attributes. see prof_attr(4)/d' \
 115                 -e '/^# See prof_attr(4)/d' \
 116                 -e '/^# \/etc\/security\/auth_attr/d' \
 117                 -e '/^# authorizations. see auth_attr(4)/d' \
 118                 -e '/^# authorization attributes. see auth_attr(4)/d' \
 119                     $4.old > $4.$$
 120         $mv_cmd $4.$$ $4.old
 121 #
 122 # Retain old and new header comments.
 123 #
 124         $sed_cmd -n -e '/^[^#]/,$d' -e '/^##/,$d' -e p $4.old > $4
 125         $rm_cmd $4.old
 126         $sed_cmd -n -e '/^[^#]/,$d' -e '/^##/,$d' -e p $3 >> $4
 127 #
 128 # If the output file now has both Sun and Oracle copyrights, remove
 129 # the Sun copyright.
 130 #
 131         sun_cr=`${egrep_cmd} '^# Copyright.*Sun Microsystems, Inc.' \
 132             $4 2>/dev/null`
 133         oracle_cr=`${egrep_cmd} '^# Copyright.*Oracle and/or its affiliates.' \
 134             $4 2>/dev/null`
 135         if [ -n "${sun_cr}" ] && [ -n "${oracle_cr}" ]; then
 136                 $sed_cmd -e '/^# Copyright.*Sun Microsystems, Inc./d' \
 137                     -e '/^# All rights reserved./d' \
 138                     -e '/^# Use is subject to license terms./d' \
 139                     $4 > $4.$$ 2>/dev/null
 140                 $mv_cmd $4.$$ $4
 141         fi
 142 #
 143 # Handle line continuations (trailing \)
 144 #
 145         $sed_cmd \
 146             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 147             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 148             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 149             $2 > $4.old
 150         $sed_cmd \
 151             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 152             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 153             -e '/\\$/{N;s/\\\n//;}'  -e '/\\$/{N;s/\\\n//;}' \
 154             $3 > $4.new
 155 #
 156 # The nawk script below processes the old and new files using up to
 157 # three passes.  If the old file is empty, only the final pass over
 158 # the new file is required.
 159 #
 160         if [ -s $4.old ]; then
 161                 nawk_pass1=$4.old
 162                 nawk_pass2=$4.new
 163                 nawk_pass3=$4.new
 164         else
 165                 nawk_pass1=
 166                 nawk_pass2=
 167                 nawk_pass3=$4.new
 168         fi
 169 #
 170 #!/usr/bin/nawk -f
 171 #
 172 #       dbmerge type=[auth|prof|user|exec] [ old-file new-file ] new-file
 173 #
 174 #       Merge two versions of an RBAC database file. The output
 175 #       consists of the lines from the new-file, while preserving
 176 #       user customizations in the old-file.
 177 #
 178 #       Entries in the new-file replace corresponding entries in the
 179 #       old-file, except as follows:  For exec_attr, all old entries
 180 #       for profiles contained in the new-file are discarded.  For
 181 #       user_attr, the "root" entry from the old-file is retained,
 182 #       and new keywords from the new-file are merged into it.
 183 #
 184 #       Records with the same key field(s) are merged, so that the
 185 #       keyword/value section of each output record contains the union
 186 #       of the keywords found in all input records with the same key
 187 #       field(s).  For selected multi-value keywords [1] the values from
 188 #       the new-file are merged with retained values from the old-file.
 189 #       Otherwise, the value for each keyword is the final value found
 190 #       in the new-file, except for keywords in the user_attr entry for
 191 #       "root" where values from the old-file are always retained.
 192 #
 193 #       [1] The following file type and keyword combinations are merged:
 194 #           prof_attr: auths, profiles, privs
 195 #           user_attr: auths, profiles, roles
 196 #
 197 #       The output is run through sort except for the comments
 198 #       which will appear first in the output.
 199 #
 200 #
 201         $nawk_cmd  '
 202 
 203 # This script may be invoked with up to three file names.  Each file
 204 # name corresponds to a separate processing pass.  The passes are
 205 # defined as follows:
 206 #
 207 # Pass 1: Read existing data.
 208 # Data from the old-file is read into memory.
 209 #
 210 # Pass 2: Remove obsolete data.
 211 # Discard any data from the old-file that is part of profiles that
 212 # are also in the new-file.  (As a special case, the user_attr entry
 213 # for 'root' is always retained.)
 214 #
 215 # Pass 3: Merge new data.
 216 # Data from the new-file is merged with the remaining old-file data.
 217 # (As a special case, exec_attr entries are replaced, not merged.)
 218 
 219 BEGIN {
 220         # The variable 'pass' specifies which type of processing to perform.
 221         # When processing only one file, skip passes 1 and 2.
 222         if (ARGC == 3)
 223                 pass += 2;
 224 
 225         # The array 'keyword_behavior' specifies the special treatment of
 226         # [type, keyword] combinations subject to value merging.
 227         keyword_behavior["prof", "auths"] =     "merge";
 228         keyword_behavior["prof", "profiles"] =  "merge";
 229         keyword_behavior["prof", "privs"] =     "merge";
 230         keyword_behavior["user", "auths"] =     "merge";
 231         keyword_behavior["user", "profiles"] =  "merge";
 232         keyword_behavior["user", "roles"] =     "merge";
 233 
 234         FS=":"
 235 }
 236 
 237 # When FNR (current file record number) is 1 it indicates that nawk
 238 # is starting to read the next file specified on its command line,
 239 # and is beginning the next processing pass.
 240 FNR == 1 {
 241         pass++;
 242 }
 243 
 244 /^#/ || /^$/ {
 245         next;
 246 }
 247 
 248 {
 249         # For each input line, nawk automatically assigns the complete
 250         # line to $0 and also splits the line at field separators and
 251         # assigns each field to a variable $1..$n.  Assignment to $0
 252         # re-splits the line into the field variables.  Conversely,
 253         # assignment to a variable $1..$n will cause $0 to be recomputed
 254         # from the field variable values.
 255         #
 256         # This code adds awareness of escaped field separators by using
 257         # a custom function to split the line into a temporary array.
 258         # It assigns the empty string to $0 to clear any excess field
 259         # variables, and assigns the desired elements of the temporary
 260         # array back to the field variables $1..$7.
 261         #
 262         # Subsequent code must not assign directly to $0 or the fields
 263         # will be re-split without regard to escaped field separators.
 264         split_escape($0, f, ":");
 265         $0 = "";
 266         $1 = f[1];
 267         $2 = f[2];
 268         $3 = f[3];
 269         $4 = f[4];
 270         $5 = f[5];
 271         $6 = f[6];
 272         $7 = f[7];
 273 }
 274 
 275 type == "auth" {
 276         key = $1 ":" $2 ":" $3 ;
 277         if (pass == 1) {
 278                 short_comment[key] = $4 ;
 279                 long_comment[key] = $5;
 280                 record[key] = $6;
 281         } else if (pass == 2) {
 282                 delete short_comment[key];
 283                 delete long_comment[key];
 284                 delete record[key];
 285         } else if (pass == 3) {
 286                 if ( $4 != "" ) {
 287                         short_comment[key] = $4 ;
 288                 }
 289                 if ( $5 != "" ) {
 290                         long_comment[key] =  $5 ;
 291                 }
 292                 record[key] = merge_attrs(record[key], $6);
 293         }
 294 }
 295 
 296 type == "prof" {
 297         key = $1 ":" $2 ":" $3 ;
 298         if (pass == 1) {
 299                 comment[key] = $4;
 300                 record[key] = $5;
 301         } else if (pass == 2) {
 302                 delete comment[key];
 303                 delete record[key];
 304         } else if (pass == 3) {
 305                 if ( $4 != "" ) {
 306                         comment[key] = $4 ;
 307                 }
 308                 if (key != "::") {
 309                         record[key] = merge_attrs(record[key], $5);
 310                 }
 311         }
 312 }
 313 
 314 type == "exec" {
 315         key = $1 ":" $2 ":" $3 ":" $4 ":" $5 ":" $6 ;
 316         if (pass == 1) {
 317                 record[key] = $7;
 318         } else if (pass == 2) {
 319                 # For exec_attr, deletion is based on the 'name' field only,
 320                 # so that all old entries for the profile are removed.
 321                 for (oldkey in record) {
 322                         split_escape(oldkey, oldkey_fields, ":");
 323                         if (oldkey_fields[1] == $1)
 324                                 delete record[oldkey];
 325                 }
 326         } else if (pass == 3) {
 327                 # Substitute new entries, do not merge.
 328                 record[key] = $7;
 329         }
 330 }
 331 
 332 type == "user" {
 333         key = $1 ":" $2 ":" $3 ":" $4 ;
 334         if (pass == 1) {
 335                 record[key] = $5;
 336         } else if (pass == 2) {
 337                 if ($1 != "root")
 338                         delete record[key];
 339         } else if (pass == 3) {
 340                 record[key] = merge_attrs(record[key], $5);
 341         }
 342 }
 343 
 344 END {
 345         for (key in record) {
 346                 if (type == "prof") {
 347                         if (key != "::") {
 348                                 print key ":" comment[key] ":" record[key];
 349                         }
 350                 } else
 351                         if (type == "auth") {
 352                                 print key ":" short_comment[key] ":"  \
 353                                     long_comment[key] ":" record[key];
 354                         } else
 355                                 print key ":" record[key];
 356                 }
 357 }
 358 
 359 function merge_attrs(old, new, cnt, new_cnt, i, j, list, new_list, keyword)
 360 {
 361         cnt = split_escape(old, list, ";");
 362         new_cnt = split_escape(new, new_list, ";");
 363         for (i = 1; i <= new_cnt; i++) {
 364                 keyword = substr(new_list[i], 1, index(new_list[i], "=")-1);
 365                 for (j = 1; j <= cnt; j++) {
 366                         if (match(list[j], "^" keyword "=")) {
 367                                 list[j] = merge_values(keyword, list[j],
 368                                     new_list[i]);
 369                                 break;
 370                         }
 371                 }
 372                 if (j > cnt)
 373                         list[++cnt] = new_list[i];
 374         }
 375 
 376         return unsplit(list, cnt, ";"); \
 377 }
 378 
 379 function merge_values(keyword, old, new, cnt, new_cnt, i, j, list, new_list, d)
 380 {
 381         # Keywords with multivalued attributes that are subject to merging
 382         # are processed by the algorithm implemented further below.
 383         # Otherwise, the keyword is not subject to merging, and:
 384         #   For user_attr, the existing value is retained.
 385         #   For any other file, the new value is substituted.
 386         if (keyword_behavior[type, keyword] != "merge") {
 387                 if (type == "user") {
 388                         return old;
 389                 } else {
 390                         return new;
 391                 }
 392         }
 393 
 394         cnt = split(substr(old, length(keyword)+2), list, ",");
 395         new_cnt = split(substr(new, length(keyword)+2), new_list, ",");
 396 
 397         # If the existing list contains "All", remove it and add it
 398         # to the new list; that way "All" will appear at the only valid
 399         # location, the end of the list.
 400         if (keyword == "profiles") {
 401                 d = 0;
 402                 for (i = 1; i <= cnt; i++) {
 403                         if (list[i] != "All")
 404                                 list[++d] = list[i];
 405                 }
 406                 if (cnt != d) {
 407                         new_list[++new_cnt] = "All";
 408                         cnt = d;
 409                 }
 410         }
 411         for (i = 1; i <= new_cnt; i++) {
 412                 for (j = 1; j <= cnt; j++) {
 413                         if (list[j] == new_list[i])
 414                                 break;
 415                 }
 416                 if (j > cnt)
 417                         list[++cnt] = new_list[i];
 418         }
 419 
 420         return keyword "=" unsplit(list, cnt, ",");
 421 }
 422 
 423 # This function is similar to the nawk built-in split() function,
 424 # except that a "\" character may be used to escape any subsequent
 425 # character, so that the escaped character will not be treated as a
 426 # field separator or as part of a field separator regular expression.
 427 # The "\" characters will remain in the elements of the output array
 428 # variable upon completion.
 429 function split_escape(str, list, fs, cnt, saved, sep)
 430 {
 431         # default to global FS
 432         if (fs == "")
 433                 fs = FS;
 434         # initialize empty list, cnt, saved
 435         split("", list, " ");
 436         cnt = 0;
 437         saved = "";
 438         # track whether last token was a field separator
 439         sep = 0;
 440         # nonzero str length indicates more string left to scan
 441         while (length(str)) {
 442                 if (match(str, fs) == 1) {
 443                         # field separator, terminates current field
 444                         list[++cnt] = saved;
 445                         saved = "";
 446                         str = substr(str, RLENGTH + 1);
 447                         sep = 1;
 448                 } else if (substr(str, 1, 1) == "\\") {
 449                         # escaped character
 450                         saved = saved substr(str, 1, 2);
 451                         str = substr(str, 3);
 452                         sep = 0;
 453                 } else {
 454                         # regular character
 455                         saved = saved substr(str, 1, 1);
 456                         str = substr(str, 2);
 457                         sep = 0;
 458                 }
 459         }
 460         # if required, append final field to list
 461         if (sep || length(saved))
 462                 list[++cnt] = saved;
 463 
 464         return cnt;
 465 }
 466 
 467 function unsplit(list, cnt, delim, str)
 468 {
 469         str = list[1];
 470         for (i = 2; i <= cnt; i++)
 471                 str = str delim list[i];
 472         return str;
 473 }' \
 474         type=$1 $nawk_pass1 $nawk_pass2 $nawk_pass3 > $4.unsorted
 475         rc=$?
 476         $sort_cmd < $4.unsorted >> $4
 477         return $rc
 478 }
 479 
 480 # $1 is the merged file
 481 # $2 is the target file
 482 #
 483 commit() {
 484         # Make sure that the last mv uses rename(2) by first moving to
 485         # the same filesystem.
 486         $mv_cmd $1 $2.$$
 487         $mv_cmd $2.$$ $2
 488         return $?
 489 }
 490 
 491 outfile=""
 492 type=""
 493 set_type_and_outfile() {
 494         #
 495         # Assumes basename $1 returns one of
 496         # prof_attr, exec_attr, auth_attr, or user_attr
 497         #
 498         fname=`$basename_cmd $1`
 499         type=`echo $fname | $sed_cmd -e s'/^\([a-z][a-z]*\)_attr$/\1/' `
 500         case "$type" in
 501                 "prof"|"exec"|"user"|"auth") ;;
 502                 *) return 2 ;;
 503         esac
 504 
 505         outfile=$tmp_dir/rbac_${PKGINST}_${fname}_merge.$$
 506 
 507         return 0
 508 }
 509 
 510 cleanup() {
 511         $rm_cmd -f $outfile $outfile.old $outfile.new $outfile.unsorted
 512 
 513         return 0
 514 }
 515 
 516 exit_status=0
 517 
 518 # main
 519 
 520 while read newfile oldfile ; do
 521         if [ -n "$PKGINST" ]
 522         then
 523                 # Install the file in the "fragment" directory.
 524                 mkdir -m 755 -p ${oldfile}.d
 525                 rm -f ${oldfile}.d/"$PKGINST"
 526                 cp $newfile ${oldfile}.d/"$PKGINST"
 527 
 528                 # Make sure that it is marked read-only.
 529                 chmod a-w,a+r ${oldfile}.d/"$PKGINST"
 530 
 531                 # We also execute the rest of the i.rbac script.
 532         fi
 533 
 534         if [ ! -f $oldfile ]; then
 535                 cp $newfile $oldfile
 536         else
 537                 set_type_and_outfile $newfile ||
 538                         set_type_and_outfile $oldfile
 539                 if [ $? -ne 0 ]; then
 540                         echo "$0 : $newfile not one of" \
 541                             " prof_attr, exec_attr, auth_attr, user_attr"
 542                         exit_status=2
 543                         continue
 544                 fi
 545 
 546                 dbmerge $type $oldfile $newfile $outfile
 547                 if [ $? -ne 0 ]; then
 548                         echo "$0 : failed to merge $newfile with $oldfile"
 549                         cleanup
 550                         exit_status=2
 551                         continue
 552                 fi
 553 
 554                 commit $outfile $oldfile
 555                 if [ $? -ne 0 ]; then
 556                         echo "$0 : failed to mv $outfile to $2"
 557                         cleanup
 558                         exit_status=2
 559                         continue
 560                 fi
 561 
 562                 cleanup
 563         fi
 564 done
 565 
 566 if [ "$1" = "ENDOFCLASS" ]; then
 567         exit 0
 568 fi
 569 
 570 exit $exit_status