1/* 2 Copyright (c) 2010 Frank Lahm <franklahm@gmail.com> 3 Copyright (c) 2011 Laura Mueller <laura-mueller@uni-duesseldorf.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14*/ 15 16#ifdef HAVE_CONFIG_H 17#include "config.h" 18#endif /* HAVE_CONFIG_H */ 19 20#ifdef HAVE_ACLS 21 22#include <unistd.h> 23#include <sys/types.h> 24#include <sys/stat.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <time.h> 29#include <errno.h> 30#include <sys/acl.h> 31 32#include <atalk/logger.h> 33#include <atalk/afp.h> 34#include <atalk/util.h> 35#include <atalk/acl.h> 36#include <atalk/unix.h> 37 38#ifdef HAVE_SOLARIS_ACLS 39 40/* Get ACL. Allocates storage as needed. Caller must free. 41 * Returns no of ACEs or -1 on error. */ 42int get_nfsv4_acl(const char *name, ace_t **retAces) 43{ 44 int ace_count = -1; 45 ace_t *aces; 46 struct stat st; 47 48 *retAces = NULL; 49 /* Only call acl() for regular files and directories, otherwise just return 0 */ 50 if (lstat(name, &st) != 0) { 51 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): %s", getcwdpath(), name, strerror(errno)); 52 return -1; 53 } 54 55 if (S_ISLNK(st.st_mode)) 56 /* sorry, no ACLs for symlinks */ 57 return 0; 58 59 if ( ! (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))) { 60 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): special", getcwdpath(), name); 61 return 0; 62 } 63 64 if ((ace_count = acl(name, ACE_GETACLCNT, 0, NULL)) == 0) { 65 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): 0 ACEs", getcwdpath(), name); 66 return 0; 67 } 68 69 if (ace_count == -1) { 70 LOG(log_debug, logtype_afpd, "get_nfsv4_acl: acl('%s/%s', ACE_GETACLCNT): ace_count %i, error: %s", 71 getcwdpath(), name, ace_count, strerror(errno)); 72 return -1; 73 } 74 75 aces = malloc(ace_count * sizeof(ace_t)); 76 if (aces == NULL) { 77 LOG(log_error, logtype_afpd, "get_nfsv4_acl: malloc error"); 78 return -1; 79 } 80 81 if ( (acl(name, ACE_GETACL, ace_count, aces)) == -1 ) { 82 LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACL) error"); 83 free(aces); 84 return -1; 85 } 86 87 LOG(log_debug9, logtype_afpd, "get_nfsv4_acl: file: %s -> No. of ACEs: %d", name, ace_count); 88 *retAces = aces; 89 90 return ace_count; 91} 92 93/* 94 Concatenate ACEs 95*/ 96ace_t *concat_aces(ace_t *aces1, int ace1count, ace_t *aces2, int ace2count) 97{ 98 ace_t *new_aces; 99 int i, j; 100 101 /* malloc buffer for new ACL */ 102 if ((new_aces = malloc((ace1count + ace2count) * sizeof(ace_t))) == NULL) { 103 LOG(log_error, logtype_afpd, "combine_aces: malloc %s", strerror(errno)); 104 return NULL; 105 } 106 107 /* Copy ACEs from buf1 */ 108 for (i=0; i < ace1count; ) { 109 memcpy(&new_aces[i], &aces1[i], sizeof(ace_t)); 110 i++; 111 } 112 113 j = i; 114 115 /* Copy ACEs from buf2 */ 116 for (i=0; i < ace2count; ) { 117 memcpy(&new_aces[j], &aces2[i], sizeof(ace_t)); 118 i++; 119 j++; 120 } 121 return new_aces; 122} 123 124/* 125 Remove any trivial ACE "in-place". Returns no of non-trivial ACEs 126*/ 127int strip_trivial_aces(ace_t **saces, int sacecount) 128{ 129 int i,j; 130 int nontrivaces = 0; 131 ace_t *aces = *saces; 132 ace_t *new_aces; 133 134 if (aces == NULL || sacecount <= 0) 135 return 0; 136 137 /* Count non-trivial ACEs */ 138 for (i=0; i < sacecount; ) { 139 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) 140 nontrivaces++; 141 i++; 142 } 143 /* malloc buffer for new ACL */ 144 if ((new_aces = malloc(nontrivaces * sizeof(ace_t))) == NULL) { 145 LOG(log_error, logtype_afpd, "strip_trivial_aces: malloc %s", strerror(errno)); 146 return -1; 147 } 148 149 /* Copy non-trivial ACEs */ 150 for (i=0, j=0; i < sacecount; ) { 151 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) { 152 memcpy(&new_aces[j], &aces[i], sizeof(ace_t)); 153 j++; 154 } 155 i++; 156 } 157 158 free(aces); 159 *saces = new_aces; 160 161 LOG(log_debug7, logtype_afpd, "strip_trivial_aces: non-trivial ACEs: %d", nontrivaces); 162 163 return nontrivaces; 164} 165 166/* 167 Remove non-trivial ACEs "in-place". Returns no of trivial ACEs. 168*/ 169int strip_nontrivial_aces(ace_t **saces, int sacecount) 170{ 171 int i,j; 172 int trivaces = 0; 173 ace_t *aces = *saces; 174 ace_t *new_aces; 175 176 /* Count trivial ACEs */ 177 for (i=0; i < sacecount; ) { 178 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) 179 trivaces++; 180 i++; 181 } 182 /* malloc buffer for new ACL */ 183 if ((new_aces = malloc(trivaces * sizeof(ace_t))) == NULL) { 184 LOG(log_error, logtype_afpd, "strip_nontrivial_aces: malloc %s", strerror(errno)); 185 return -1; 186 } 187 188 /* Copy trivial ACEs */ 189 for (i=0, j=0; i < sacecount; ) { 190 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) { 191 memcpy(&new_aces[j], &aces[i], sizeof(ace_t)); 192 j++; 193 } 194 i++; 195 } 196 /* Free old ACEs */ 197 free(aces); 198 *saces = new_aces; 199 200 LOG(log_debug7, logtype_afpd, "strip_nontrivial_aces: trivial ACEs: %d", trivaces); 201 202 return trivaces; 203} 204 205/*! 206 * Change mode of file preserving existing explicit ACEs 207 * 208 * nfsv4_chmod 209 * (1) reads objects ACL (acl1), may return 0 or -1 NFSv4 ACEs on eg UFS fs 210 * (2) removes all trivial ACEs from the ACL by calling strip_trivial_aces(), possibly 211 * leaving 0 ACEs in the ACL if there were only trivial ACEs as mapped from the mode 212 * (3) calls chmod() with mode, we're done if step (1) returned 0 for noaces 213 * (4) reads the changed ACL (acl2) which 214 * a) might still contain explicit ACEs (up to onnv132) 215 * b) will have any explicit ACE removed (starting with onnv145/Openindiana) 216 * (5) strip any explicit ACE from acl2 using strip_nontrivial_aces() 217 * (6) merge acl2 and acl2 218 * (7) set the ACL merged ACL on the object 219 */ 220int nfsv4_chmod(char *name, mode_t mode) 221{ 222 int ret = -1; 223 int noaces, nnaces; 224 ace_t *oacl = NULL, *nacl = NULL, *cacl = NULL; 225 226 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o)", 227 getcwdpath(), name, mode); 228 229 if ((noaces = get_nfsv4_acl(name, &oacl)) < 1) /* (1) */ 230 return chmod(name, mode); 231 232 if ((noaces = strip_trivial_aces(&oacl, noaces)) == -1) /* (2) */ 233 goto exit; 234 235 if (chmod(name, mode) != 0) /* (3) */ 236 goto exit; 237 238 if ((nnaces = get_nfsv4_acl(name, &nacl)) == -1) {/* (4) */ 239 if (errno != EACCES) 240 goto exit; 241 become_root(); 242 nnaces = get_nfsv4_acl(name, &nacl); 243 unbecome_root(); 244 if (nnaces == -1) 245 goto exit; 246 } 247 248 if ((nnaces = strip_nontrivial_aces(&nacl, nnaces)) == -1) /* (5) */ 249 goto exit; 250 251 if ((cacl = concat_aces(oacl, noaces, nacl, nnaces)) == NULL) /* (6) */ 252 goto exit; 253 254 if ((ret = acl(name, ACE_SETACL, noaces + nnaces, cacl)) != 0) { 255 if (errno != EACCES) { 256 LOG(log_error, logtype_afpd, "nfsv4_chmod: error setting acl: %s", strerror(errno)); 257 goto exit; 258 } 259 become_root(); 260 ret = acl(name, ACE_SETACL, noaces + nnaces, cacl); 261 unbecome_root(); 262 if (ret != 0) { 263 LOG(log_error, logtype_afpd, "nfsv4_chmod: error setting acl: %s", strerror(errno)); 264 goto exit; 265 } 266 } 267 268exit: 269 if (oacl) free(oacl); 270 if (nacl) free(nacl); 271 if (cacl) free(cacl); 272 273 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o): result: %d", 274 getcwdpath(), name, mode, ret); 275 276 return ret; 277} 278 279#endif /* HAVE_SOLARIS_ACLS */ 280 281#ifdef HAVE_POSIX_ACLS 282 283/* This is a workaround for chmod() on filestystems supporting Posix 1003.1e draft 17 284 * compliant ACLs. For objects with extented ACLs, eg objects with an ACL_MASK entry, 285 * chmod() manipulates ACL_MASK instead of ACL_GROUP_OBJ. As OS X isn't aware of 286 * this behavior calling FPSetFileDirParms may lead to unpredictable results. For 287 * more information see section 23.1.2 of Posix 1003.1e draft 17. 288 * 289 * posix_chmod() accepts the same arguments as chmod() and returns 0 in case of 290 * success or -1 in case something went wrong. 291 */ 292 293#define SEARCH_GROUP_OBJ 0x01 294#define SEARCH_MASK 0x02 295 296int posix_chmod(const char *name, mode_t mode) { 297 int ret = 0; 298 int entry_id = ACL_FIRST_ENTRY; 299 acl_entry_t entry; 300 acl_entry_t group_entry; 301 acl_tag_t tag; 302 acl_t acl; 303 u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */ 304 305 LOG(log_maxdebug, logtype_afpd, "posix_chmod: %s mode: 0x%08x", name, mode); 306 307 /* Call chmod() first because there might be some special bits to be set which 308 * aren't related to access control. 309 */ 310 ret = chmod(name, mode); 311 312 if (ret) 313 goto done; 314 315 /* Check if the underlying filesystem supports ACLs. */ 316 acl = acl_get_file(name, ACL_TYPE_ACCESS); 317 318 if (acl) { 319 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */ 320 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) { 321 entry_id = ACL_NEXT_ENTRY; 322 323 ret = acl_get_tag_type(entry, &tag); 324 325 if (ret) { 326 LOG(log_error, logtype_afpd, "posix_chmod: Failed to get tag type."); 327 goto cleanup; 328 } 329 330 switch (tag) { 331 case ACL_GROUP_OBJ: 332 group_entry = entry; 333 not_found &= ~SEARCH_GROUP_OBJ; 334 break; 335 336 case ACL_MASK: 337 not_found &= ~SEARCH_MASK; 338 break; 339 340 default: 341 break; 342 } 343 } 344 if (!not_found) { 345 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ 346 * with the group permissions. 347 */ 348 acl_permset_t permset; 349 acl_perm_t perm = 0; 350 351 ret = acl_get_permset(group_entry, &permset); 352 353 if (ret) { 354 LOG(log_error, logtype_afpd, "posix_chmod: Can't get permset."); 355 goto cleanup; 356 } 357 ret = acl_clear_perms(permset); 358 359 if (ret) 360 goto cleanup; 361 362 if (mode & S_IXGRP) 363 perm |= ACL_EXECUTE; 364 365 if (mode & S_IWGRP) 366 perm |= ACL_WRITE; 367 368 if (mode & S_IRGRP) 369 perm |= ACL_READ; 370 371 ret = acl_add_perm(permset, perm); 372 373 if (ret) 374 goto cleanup; 375 376 ret = acl_set_permset(group_entry, permset); 377 378 if (ret) { 379 LOG(log_error, logtype_afpd, "posix_chmod: Can't set permset."); 380 goto cleanup; 381 } 382 /* also update ACL_MASK */ 383 ret = acl_calc_mask(&acl); 384 385 if (ret) { 386 LOG(log_error, logtype_afpd, "posix_chmod: acl_calc_mask failed."); 387 goto cleanup; 388 } 389 ret = acl_set_file(name, ACL_TYPE_ACCESS, acl); 390 } 391cleanup: 392 acl_free(acl); 393 } 394done: 395 LOG(log_maxdebug, logtype_afpd, "posix_chmod: %d", ret); 396 return ret; 397} 398 399/* 400 * posix_fchmod() accepts the same arguments as fchmod() and returns 0 in case of 401 * success or -1 in case something went wrong. 402 */ 403int posix_fchmod(int fd, mode_t mode) { 404 int ret = 0; 405 int entry_id = ACL_FIRST_ENTRY; 406 acl_entry_t entry; 407 acl_entry_t group_entry; 408 acl_tag_t tag; 409 acl_t acl; 410 u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */ 411 412 /* Call chmod() first because there might be some special bits to be set which 413 * aren't related to access control. 414 */ 415 ret = fchmod(fd, mode); 416 417 if (ret) 418 goto done; 419 420 /* Check if the underlying filesystem supports ACLs. */ 421 acl = acl_get_fd(fd); 422 423 if (acl) { 424 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */ 425 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) { 426 entry_id = ACL_NEXT_ENTRY; 427 428 ret = acl_get_tag_type(entry, &tag); 429 430 if (ret) { 431 LOG(log_error, logtype_afpd, "posix_fchmod: Failed to get tag type."); 432 goto cleanup; 433 } 434 435 switch (tag) { 436 case ACL_GROUP_OBJ: 437 group_entry = entry; 438 not_found &= ~SEARCH_GROUP_OBJ; 439 break; 440 441 case ACL_MASK: 442 not_found &= ~SEARCH_MASK; 443 break; 444 445 default: 446 break; 447 } 448 } 449 if (!not_found) { 450 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ 451 * with the group permissions. 452 */ 453 acl_permset_t permset; 454 acl_perm_t perm = 0; 455 456 ret = acl_get_permset(group_entry, &permset); 457 458 if (ret) { 459 LOG(log_error, logtype_afpd, "posix_fchmod: Can't get permset."); 460 goto cleanup; 461 } 462 ret = acl_clear_perms(permset); 463 464 if (ret) 465 goto cleanup; 466 467 if (mode & S_IXGRP) 468 perm |= ACL_EXECUTE; 469 470 if (mode & S_IWGRP) 471 perm |= ACL_WRITE; 472 473 if (mode & S_IRGRP) 474 perm |= ACL_READ; 475 476 ret = acl_add_perm(permset, perm); 477 478 if (ret) 479 goto cleanup; 480 481 ret = acl_set_permset(group_entry, permset); 482 483 if (ret) { 484 LOG(log_error, logtype_afpd, "posix_fchmod: Can't set permset."); 485 goto cleanup; 486 } 487 /* also update ACL_MASK */ 488 ret = acl_calc_mask(&acl); 489 490 if (ret) { 491 LOG(log_error, logtype_afpd, "posix_fchmod: acl_calc_mask failed."); 492 goto cleanup; 493 } 494 ret = acl_set_fd(fd, acl); 495 } 496cleanup: 497 acl_free(acl); 498 } 499done: 500 return ret; 501} 502 503#endif /* HAVE_POSIX_ACLS */ 504 505#endif /* HAVE_ACLS */ 506