1/* 2 Copyright (c) 2010 Frank Lahm <franklahm@gmail.com> 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 2 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13*/ 14 15#ifdef HAVE_CONFIG_H 16#include "config.h" 17#endif /* HAVE_CONFIG_H */ 18 19#ifdef HAVE_SOLARIS_ACLS 20 21#include <unistd.h> 22#include <sys/types.h> 23#include <sys/stat.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <time.h> 28#include <errno.h> 29#include <sys/acl.h> 30 31#include <atalk/logger.h> 32#include <atalk/afp.h> 33#include <atalk/util.h> 34#include <atalk/acl.h> 35 36/* Get ACL. Allocates storage as needed. Caller must free. 37 * Returns no of ACEs or -1 on error. */ 38int get_nfsv4_acl(const char *name, ace_t **retAces) 39{ 40 int ace_count = -1; 41 ace_t *aces; 42 struct stat st; 43 44 *retAces = NULL; 45 /* Only call acl() for regular files and directories, otherwise just return 0 */ 46 if (lstat(name, &st) != 0) { 47 LOG(log_warning, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): %s", getcwdpath(), name, strerror(errno)); 48 return -1; 49 } 50 51 if (S_ISLNK(st.st_mode)) 52 /* sorry, no ACLs for symlinks */ 53 return 0; 54 55 if ( ! (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))) { 56 LOG(log_warning, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): special", getcwdpath(), name); 57 return 0; 58 } 59 60 if ((ace_count = acl(name, ACE_GETACLCNT, 0, NULL)) == 0) { 61 LOG(log_warning, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): 0 ACEs", getcwdpath(), name); 62 return 0; 63 } 64 65 if (ace_count == -1) { 66 LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl('%s/%s', ACE_GETACLCNT): ace_count %i, error: %s", 67 getcwdpath(), name, ace_count, strerror(errno)); 68 return -1; 69 } 70 71 aces = malloc(ace_count * sizeof(ace_t)); 72 if (aces == NULL) { 73 LOG(log_error, logtype_afpd, "get_nfsv4_acl: malloc error"); 74 return -1; 75 } 76 77 if ( (acl(name, ACE_GETACL, ace_count, aces)) == -1 ) { 78 LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACL) error"); 79 free(aces); 80 return -1; 81 } 82 83 LOG(log_debug9, logtype_afpd, "get_nfsv4_acl: file: %s -> No. of ACEs: %d", name, ace_count); 84 *retAces = aces; 85 86 return ace_count; 87} 88 89/* 90 Concatenate ACEs 91*/ 92ace_t *concat_aces(ace_t *aces1, int ace1count, ace_t *aces2, int ace2count) 93{ 94 ace_t *new_aces; 95 int i, j; 96 97 /* malloc buffer for new ACL */ 98 if ((new_aces = malloc((ace1count + ace2count) * sizeof(ace_t))) == NULL) { 99 LOG(log_error, logtype_afpd, "combine_aces: malloc %s", strerror(errno)); 100 return NULL; 101 } 102 103 /* Copy ACEs from buf1 */ 104 for (i=0; i < ace1count; ) { 105 memcpy(&new_aces[i], &aces1[i], sizeof(ace_t)); 106 i++; 107 } 108 109 j = i; 110 111 /* Copy ACEs from buf2 */ 112 for (i=0; i < ace2count; ) { 113 memcpy(&new_aces[j], &aces2[i], sizeof(ace_t)); 114 i++; 115 j++; 116 } 117 return new_aces; 118} 119 120/* 121 Remove any trivial ACE "in-place". Returns no of non-trivial ACEs 122*/ 123int strip_trivial_aces(ace_t **saces, int sacecount) 124{ 125 int i,j; 126 int nontrivaces = 0; 127 ace_t *aces = *saces; 128 ace_t *new_aces; 129 130 if (aces == NULL || sacecount <= 0) 131 return 0; 132 133 /* Count non-trivial ACEs */ 134 for (i=0; i < sacecount; ) { 135 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) 136 nontrivaces++; 137 i++; 138 } 139 /* malloc buffer for new ACL */ 140 if ((new_aces = malloc(nontrivaces * sizeof(ace_t))) == NULL) { 141 LOG(log_error, logtype_afpd, "strip_trivial_aces: malloc %s", strerror(errno)); 142 return -1; 143 } 144 145 /* Copy non-trivial ACEs */ 146 for (i=0, j=0; i < sacecount; ) { 147 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) { 148 memcpy(&new_aces[j], &aces[i], sizeof(ace_t)); 149 j++; 150 } 151 i++; 152 } 153 154 free(aces); 155 *saces = new_aces; 156 157 LOG(log_debug7, logtype_afpd, "strip_trivial_aces: non-trivial ACEs: %d", nontrivaces); 158 159 return nontrivaces; 160} 161 162/* 163 Remove non-trivial ACEs "in-place". Returns no of trivial ACEs. 164*/ 165int strip_nontrivial_aces(ace_t **saces, int sacecount) 166{ 167 int i,j; 168 int trivaces = 0; 169 ace_t *aces = *saces; 170 ace_t *new_aces; 171 172 /* Count trivial ACEs */ 173 for (i=0; i < sacecount; ) { 174 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) 175 trivaces++; 176 i++; 177 } 178 /* malloc buffer for new ACL */ 179 if ((new_aces = malloc(trivaces * sizeof(ace_t))) == NULL) { 180 LOG(log_error, logtype_afpd, "strip_nontrivial_aces: malloc %s", strerror(errno)); 181 return -1; 182 } 183 184 /* Copy trivial ACEs */ 185 for (i=0, j=0; i < sacecount; ) { 186 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) { 187 memcpy(&new_aces[j], &aces[i], sizeof(ace_t)); 188 j++; 189 } 190 i++; 191 } 192 /* Free old ACEs */ 193 free(aces); 194 *saces = new_aces; 195 196 LOG(log_debug7, logtype_afpd, "strip_nontrivial_aces: trivial ACEs: %d", trivaces); 197 198 return trivaces; 199} 200 201/*! 202 * Change mode of file preserving existing explicit ACEs 203 * 204 * nfsv4_chmod 205 * (1) reads objects ACL (acl1) 206 * (2) removes all trivial ACEs from the ACL by calling strip_trivial_aces(), possibly 207 * leaving 0 ACEs in the ACL if there were only trivial ACEs as mapped from the mode 208 * (3) calls chmod() with mode 209 * (4) reads the changed ACL (acl2) which 210 * a) might still contain explicit ACEs (up to onnv132) 211 * b) will have any explicit ACE removed (starting with onnv145/Openindiana) 212 * (5) strip any explicit ACE from acl2 using strip_nontrivial_aces() 213 * (6) merge acl2 and acl2 214 * (7) set the ACL merged ACL on the object 215 */ 216int nfsv4_chmod(char *name, mode_t mode) 217{ 218 int ret = -1; 219 int noaces, nnaces; 220 ace_t *oacl = NULL, *nacl = NULL, *cacl = NULL; 221 222 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o)", 223 getcwdpath(), name, mode); 224 225 if ((noaces = get_nfsv4_acl(name, &oacl)) == -1) /* (1) */ 226 goto exit; 227 if ((noaces = strip_trivial_aces(&oacl, noaces)) == -1) /* (2) */ 228 goto exit; 229 230#ifdef chmod 231#undef chmod 232#endif 233 if (chmod(name, mode) != 0) /* (3) */ 234 goto exit; 235 236 if ((nnaces = get_nfsv4_acl(name, &nacl)) == -1) /* (4) */ 237 goto exit; 238 if ((nnaces = strip_nontrivial_aces(&nacl, nnaces)) == -1) /* (5) */ 239 goto exit; 240 241 if ((cacl = concat_aces(oacl, noaces, nacl, nnaces)) == NULL) /* (6) */ 242 goto exit; 243 244 if ((ret = acl(name, ACE_SETACL, noaces + nnaces, cacl)) != 0) { 245 LOG(log_error, logtype_afpd, "nfsv4_chmod: error setting acl: %s", strerror(errno)); 246 goto exit; 247 } 248 249exit: 250 if (oacl) free(oacl); 251 if (nacl) free(nacl); 252 if (cacl) free(cacl); 253 254 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o): result: %u", 255 getcwdpath(), name, mode, ret); 256 257 return ret; 258} 259 260#endif /* HAVE_SOLARIS_ACLS */ 261