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: %04o) BEGIN", 306 fullpathname(name), mode); 307 308 /* Call chmod() first because there might be some special bits to be set which 309 * aren't related to access control. 310 */ 311#ifdef BSD4_4 312 /* 313 * On FreeBSD chmod_acl() ends up in here too, but on 314 * FreeBSD sine ~9.1 with ZFS doesn't allow setting the g+s bit. 315 * Fixes PR #491. 316 */ 317 mode &= 0777; 318#endif 319 ret = chmod(name, mode); 320 321 if (ret) 322 goto done; 323 324 /* Check if the underlying filesystem supports ACLs. */ 325 acl = acl_get_file(name, ACL_TYPE_ACCESS); 326 327 if (acl) { 328 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */ 329 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) { 330 entry_id = ACL_NEXT_ENTRY; 331 332 ret = acl_get_tag_type(entry, &tag); 333 334 if (ret) { 335 LOG(log_error, logtype_afpd, "posix_chmod: Failed to get tag type."); 336 goto cleanup; 337 } 338 339 switch (tag) { 340 case ACL_GROUP_OBJ: 341 group_entry = entry; 342 not_found &= ~SEARCH_GROUP_OBJ; 343 break; 344 345 case ACL_MASK: 346 not_found &= ~SEARCH_MASK; 347 break; 348 349 default: 350 break; 351 } 352 } 353 if (!not_found) { 354 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ 355 * with the group permissions. 356 */ 357 acl_permset_t permset; 358 acl_perm_t perm = 0; 359 360 ret = acl_get_permset(group_entry, &permset); 361 362 if (ret) { 363 LOG(log_error, logtype_afpd, "posix_chmod: Can't get permset."); 364 goto cleanup; 365 } 366 ret = acl_clear_perms(permset); 367 368 if (ret) 369 goto cleanup; 370 371 if (mode & S_IXGRP) 372 perm |= ACL_EXECUTE; 373 374 if (mode & S_IWGRP) 375 perm |= ACL_WRITE; 376 377 if (mode & S_IRGRP) 378 perm |= ACL_READ; 379 380 ret = acl_add_perm(permset, perm); 381 382 if (ret) 383 goto cleanup; 384 385 ret = acl_set_permset(group_entry, permset); 386 387 if (ret) { 388 LOG(log_error, logtype_afpd, "posix_chmod: Can't set permset."); 389 goto cleanup; 390 } 391 /* also update ACL_MASK */ 392 ret = acl_calc_mask(&acl); 393 394 if (ret) { 395 LOG(log_error, logtype_afpd, "posix_chmod: acl_calc_mask failed."); 396 goto cleanup; 397 } 398 ret = acl_set_file(name, ACL_TYPE_ACCESS, acl); 399 } 400cleanup: 401 acl_free(acl); 402 } 403done: 404 LOG(log_maxdebug, logtype_afpd, "posix_chmod(\"%s\", mode: %04o): END: %d", 405 fullpathname(name), mode, ret); 406 return ret; 407} 408 409/* 410 * posix_fchmod() accepts the same arguments as fchmod() and returns 0 in case of 411 * success or -1 in case something went wrong. 412 */ 413int posix_fchmod(int fd, mode_t mode) { 414 int ret = 0; 415 int entry_id = ACL_FIRST_ENTRY; 416 acl_entry_t entry; 417 acl_entry_t group_entry; 418 acl_tag_t tag; 419 acl_t acl; 420 u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */ 421 422 /* Call chmod() first because there might be some special bits to be set which 423 * aren't related to access control. 424 */ 425 ret = fchmod(fd, mode); 426 427 if (ret) 428 goto done; 429 430 /* Check if the underlying filesystem supports ACLs. */ 431 acl = acl_get_fd(fd); 432 433 if (acl) { 434 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */ 435 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) { 436 entry_id = ACL_NEXT_ENTRY; 437 438 ret = acl_get_tag_type(entry, &tag); 439 440 if (ret) { 441 LOG(log_error, logtype_afpd, "posix_fchmod: Failed to get tag type."); 442 goto cleanup; 443 } 444 445 switch (tag) { 446 case ACL_GROUP_OBJ: 447 group_entry = entry; 448 not_found &= ~SEARCH_GROUP_OBJ; 449 break; 450 451 case ACL_MASK: 452 not_found &= ~SEARCH_MASK; 453 break; 454 455 default: 456 break; 457 } 458 } 459 if (!not_found) { 460 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ 461 * with the group permissions. 462 */ 463 acl_permset_t permset; 464 acl_perm_t perm = 0; 465 466 ret = acl_get_permset(group_entry, &permset); 467 468 if (ret) { 469 LOG(log_error, logtype_afpd, "posix_fchmod: Can't get permset."); 470 goto cleanup; 471 } 472 ret = acl_clear_perms(permset); 473 474 if (ret) 475 goto cleanup; 476 477 if (mode & S_IXGRP) 478 perm |= ACL_EXECUTE; 479 480 if (mode & S_IWGRP) 481 perm |= ACL_WRITE; 482 483 if (mode & S_IRGRP) 484 perm |= ACL_READ; 485 486 ret = acl_add_perm(permset, perm); 487 488 if (ret) 489 goto cleanup; 490 491 ret = acl_set_permset(group_entry, permset); 492 493 if (ret) { 494 LOG(log_error, logtype_afpd, "posix_fchmod: Can't set permset."); 495 goto cleanup; 496 } 497 /* also update ACL_MASK */ 498 ret = acl_calc_mask(&acl); 499 500 if (ret) { 501 LOG(log_error, logtype_afpd, "posix_fchmod: acl_calc_mask failed."); 502 goto cleanup; 503 } 504 ret = acl_set_fd(fd, acl); 505 } 506cleanup: 507 acl_free(acl); 508 } 509done: 510 return ret; 511} 512 513#endif /* HAVE_POSIX_ACLS */ 514 515#endif /* HAVE_ACLS */ 516