1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 1999, 2000, 2001 Robert N. M. Watson 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 /* 29 * acl_from_text: Convert a text-form ACL from a string to an acl_t. 30 */ 31 32 #include <sys/cdefs.h> 33 #if 0 34 __FBSDID("$FreeBSD: head/lib/libc/posix1e/acl_from_text.c 326193 2017-11-25 17:12:48Z pfg $"); 35 #else 36 __RCSID("$NetBSD: acl_from_text.c,v 1.1 2020/05/16 18:31:47 christos Exp $"); 37 #endif 38 39 #include "namespace.h" 40 #include <sys/types.h> 41 #include <sys/acl.h> 42 #include <errno.h> 43 #include <grp.h> 44 #include <pwd.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <assert.h> 49 50 #include "acl_support.h" 51 52 static acl_tag_t acl_string_to_tag(char *tag, char *qualifier); 53 54 int _nfs4_acl_entry_from_text(acl_t aclp, char *entry); 55 int _text_could_be_nfs4_acl(const char *entry); 56 57 static acl_tag_t 58 acl_string_to_tag(char *tag, char *qualifier) 59 { 60 61 if (*qualifier == '\0') { 62 if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) { 63 return (ACL_USER_OBJ); 64 } else 65 if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) { 66 return (ACL_GROUP_OBJ); 67 } else 68 if ((!strcmp(tag, "mask")) || (!strcmp(tag, "m"))) { 69 return (ACL_MASK); 70 } else 71 if ((!strcmp(tag, "other")) || (!strcmp(tag, "o"))) { 72 return (ACL_OTHER); 73 } else 74 return(-1); 75 } else { 76 if ((!strcmp(tag, "user")) || (!strcmp(tag, "u"))) { 77 return(ACL_USER); 78 } else 79 if ((!strcmp(tag, "group")) || (!strcmp(tag, "g"))) { 80 return(ACL_GROUP); 81 } else 82 return(-1); 83 } 84 } 85 86 static int 87 _posix1e_acl_entry_from_text(acl_t aclp, char *entry) 88 { 89 acl_tag_t t; 90 acl_perm_t p; 91 char *tag, *qualifier, *permission; 92 uid_t id; 93 int error; 94 95 assert(_acl_brand(aclp) == ACL_BRAND_POSIX); 96 97 /* Split into three ':' delimited fields. */ 98 tag = strsep(&entry, ":"); 99 if (tag == NULL) { 100 errno = EINVAL; 101 return (-1); 102 } 103 tag = string_skip_whitespace(tag); 104 if ((*tag == '\0') && (!entry)) { 105 /* 106 * Is an entirely comment line, skip to next 107 * comma. 108 */ 109 return (0); 110 } 111 string_trim_trailing_whitespace(tag); 112 113 qualifier = strsep(&entry, ":"); 114 if (qualifier == NULL) { 115 errno = EINVAL; 116 return (-1); 117 } 118 qualifier = string_skip_whitespace(qualifier); 119 string_trim_trailing_whitespace(qualifier); 120 121 permission = strsep(&entry, ":"); 122 if (permission == NULL || entry) { 123 errno = EINVAL; 124 return (-1); 125 } 126 permission = string_skip_whitespace(permission); 127 string_trim_trailing_whitespace(permission); 128 129 t = acl_string_to_tag(tag, qualifier); 130 if (t == (acl_tag_t)-1) { 131 errno = EINVAL; 132 return (-1); 133 } 134 135 error = _posix1e_acl_string_to_perm(permission, &p); 136 if (error == -1) { 137 errno = EINVAL; 138 return (-1); 139 } 140 141 switch(t) { 142 case ACL_USER_OBJ: 143 case ACL_GROUP_OBJ: 144 case ACL_MASK: 145 case ACL_OTHER: 146 if (*qualifier != '\0') { 147 errno = EINVAL; 148 return (-1); 149 } 150 id = 0; 151 break; 152 153 case ACL_USER: 154 case ACL_GROUP: 155 error = _acl_name_to_id(t, qualifier, &id); 156 if (error == -1) 157 return (-1); 158 break; 159 160 default: 161 errno = EINVAL; 162 return (-1); 163 } 164 165 error = _posix1e_acl_add_entry(aclp, t, id, p); 166 if (error == -1) 167 return (-1); 168 169 return (0); 170 } 171 172 static int 173 _text_is_nfs4_entry(const char *entry) 174 { 175 int count = 0; 176 177 assert(strlen(entry) > 0); 178 179 while (*entry != '\0') { 180 if (*entry == ':' || *entry == '@') 181 count++; 182 entry++; 183 } 184 185 if (count <= 2) 186 return (0); 187 188 return (1); 189 } 190 191 /* 192 * acl_from_text -- Convert a string into an ACL. 193 * Postpone most validity checking until the end and call acl_valid() to do 194 * that. 195 */ 196 acl_t 197 acl_from_text(const char *buf_p) 198 { 199 acl_t acl; 200 char *mybuf_p, *line, *cur, *notcomment, *comment, *entry; 201 int error; 202 203 /* Local copy we can mess up. */ 204 mybuf_p = strdup(buf_p); 205 if (mybuf_p == NULL) 206 return(NULL); 207 208 acl = acl_init(3); /* XXX: WTF, 3? */ 209 if (acl == NULL) { 210 free(mybuf_p); 211 return(NULL); 212 } 213 214 /* Outer loop: delimit at \n boundaries. */ 215 cur = mybuf_p; 216 while ((line = strsep(&cur, "\n"))) { 217 /* Now split the line on the first # to strip out comments. */ 218 comment = line; 219 notcomment = strsep(&comment, "#"); 220 221 /* Inner loop: delimit at ',' boundaries. */ 222 while ((entry = strsep(¬comment, ","))) { 223 224 /* Skip empty lines. */ 225 if (strlen(string_skip_whitespace(entry)) == 0) 226 continue; 227 228 if (_acl_brand(acl) == ACL_BRAND_UNKNOWN) { 229 if (_text_is_nfs4_entry(entry)) 230 _acl_brand_as(acl, ACL_BRAND_NFS4); 231 else 232 _acl_brand_as(acl, ACL_BRAND_POSIX); 233 } 234 235 switch (_acl_brand(acl)) { 236 case ACL_BRAND_NFS4: 237 error = _nfs4_acl_entry_from_text(acl, entry); 238 break; 239 240 case ACL_BRAND_POSIX: 241 error = _posix1e_acl_entry_from_text(acl, entry); 242 break; 243 244 default: 245 error = EINVAL; 246 break; 247 } 248 249 if (error) 250 goto error_label; 251 } 252 } 253 254 #if 0 255 /* XXX Should we only return ACLs valid according to acl_valid? */ 256 /* Verify validity of the ACL we read in. */ 257 if (acl_valid(acl) == -1) { 258 errno = EINVAL; 259 goto error_label; 260 } 261 #endif 262 263 free(mybuf_p); 264 return(acl); 265 266 error_label: 267 acl_free(acl); 268 free(mybuf_p); 269 return(NULL); 270 } 271 272 /* 273 * Given a username/groupname from a text form of an ACL, return the uid/gid 274 * XXX NOT THREAD SAFE, RELIES ON GETPWNAM, GETGRNAM 275 * XXX USES *PW* AND *GR* WHICH ARE STATEFUL AND THEREFORE THIS ROUTINE 276 * MAY HAVE SIDE-EFFECTS 277 */ 278 int 279 _acl_name_to_id(acl_tag_t tag, char *name, uid_t *id) 280 { 281 struct group *g; 282 struct passwd *p; 283 unsigned long l; 284 char *endp; 285 286 switch(tag) { 287 case ACL_USER: 288 p = getpwnam(name); 289 if (p == NULL) { 290 l = strtoul(name, &endp, 0); 291 if (*endp != '\0' || l != (unsigned long)(uid_t)l) { 292 errno = EINVAL; 293 return (-1); 294 } 295 *id = (uid_t)l; 296 return (0); 297 } 298 *id = p->pw_uid; 299 return (0); 300 301 case ACL_GROUP: 302 g = getgrnam(name); 303 if (g == NULL) { 304 l = strtoul(name, &endp, 0); 305 if (*endp != '\0' || l != (unsigned long)(gid_t)l) { 306 errno = EINVAL; 307 return (-1); 308 } 309 *id = (gid_t)l; 310 return (0); 311 } 312 *id = g->gr_gid; 313 return (0); 314 315 default: 316 return (EINVAL); 317 } 318 } 319