/* * pseudo_wrappers.c, darwin pseudo wrappers * * Copyright (c) 2008-2011 Wind River Systems, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the Lesser GNU General Public License version 2.1 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the Lesser GNU General Public License for more details. * * You should have received a copy of the Lesser GNU General Public License * version 2.1 along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* we need XATTR_NOFOLLOW in scope */ #include /* shared functionality for the xattr code */ /* Each of these functions is expecting to get an optional name, and * a populated statbuf to use for sending messages to the server. */ /* to avoid namespace pollution and such, we now duplicate the * basic functionality of a POSIX ACL list, as used by libacl or * the kernel. Documentation was obtained from the headers of libacl * and from a page or two of _The Linux Programming Interface_, by * Michael Kerrisk. */ typedef struct { uint16_t tag; uint16_t perm; uint32_t id; } acl_entry; typedef struct { uint32_t version; acl_entry entries[]; } acl_header; enum acl_tags { ACL_UNDEFINED = 0x0, ACL_USER_OBJ = 0x1, ACL_USER = 0x2, ACL_GROUP_OBJ = 0x4, ACL_GROUP = 0x8, ACL_MASK = 0x10, ACL_OTHER = 0x20, }; static const int endian_test = 1; static const char *endian_tester = (char *) &endian_test; static inline int le16(int x16) { if (*endian_tester) { return x16; } else { return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8); } } static inline int le32(int x32) { if (*endian_tester) { return x32; } else { return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) | ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24); } } /* set mode to match the contents of header. Return non-zero on error. * On a zero return, mode is a valid posix mode, and *extra is set to * 1 if any of the entries are not reflected by that mode. On a non-zero * return, no promises are made about *extra or *mode. */ static int posix_permissions(const acl_header *header, int entries, int *extra, int *mode) { int acl_seen = 0; if (le32(header->version) != 2) { pseudo_diag("Fatal: ACL support no available for header version %d.\n", le32(header->version)); return 1; } *mode = 0; *extra = 0; for (int i = 0; i < entries; ++i) { const acl_entry *e = &header->entries[i]; int tag = le16(e->tag); int perm = le16(e->perm); acl_seen |= tag; switch (tag) { case ACL_USER_OBJ: *mode = *mode | (perm << 6); break; case ACL_GROUP_OBJ: *mode = *mode | (perm << 3); break; case ACL_OTHER: *mode = *mode | perm; break; case ACL_USER: case ACL_GROUP: case ACL_MASK: *extra = *extra + 1; break; default: pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n", tag); return 1; } } return 0; } #define RC_AND_BUF \ int rc; \ PSEUDO_STATBUF buf; \ if (path) { \ rc = base_lstat(path, &buf); \ } else { \ rc = base_fstat(fd, &buf); \ } \ if (rc == -1) { \ return rc; \ } /* Note: no profiling implementation yet. */ static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size, u_int32_t position, int options) { RC_AND_BUF if (!strncmp(name, "com.apple.", 10)) { if (fd != -1) { return real_fgetxattr(fd, name, value, size, position, options); } else { return real_getxattr(path, name, value, size, position, options); } } pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n", path ? path : "", fd, name); pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name); if (result->result != RESULT_SUCCEED) { errno = ENOATTR; return -1; } if (value) { pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n", result->path, result->pathlen); if (size >= result->pathlen) { memcpy(value, result->path, result->pathlen); } else { memcpy(value, result->path, size); errno = ERANGE; } } return result->pathlen; } static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, u_int32_t position, int options) { RC_AND_BUF pseudo_op_t op; pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n", path ? path : "", fd, name, (int) size, (char *) value); if (!strncmp(name, "com.apple.", 10)) { if (fd != -1) { return real_fsetxattr(fd, name, value, size, position, options); } else { return real_setxattr(path, name, value, size, position, options); } } /* this may be a plain chmod */ if (!strcmp(name, "system.posix_acl_access")) { int extra; int mode; int entries = (size - sizeof(acl_header)) / sizeof(acl_entry); if (!posix_permissions(value, entries, &extra, &mode)) { pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n", mode, extra); buf.st_mode = mode; /* we want to actually issue a corresponding chmod, * as well, or else the file ends up 0600 on the * host. Using the slightly-less-efficient wrap_chmod * avoids possible misalignment. */ if (path) { wrap_chmod(path, mode); } else { wrap_fchmod(fd, mode); } /* we are sneaky, and do not actually record this using * extended attributes. */ if (!extra) { return 0; } } } if (options & XATTR_CREATE) { op = OP_CREATE_XATTR; } else if (options & XATTR_REPLACE) { op = OP_REPLACE_XATTR; } else { op = OP_SET_XATTR; } pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size); /* we automatically assume success */ if (op == OP_SET_XATTR) { return 0; } /* CREATE/REPLACE operations can report failure */ if (!result || result->result == RESULT_FAIL) { return -1; } return 0; } static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size, int options) { RC_AND_BUF char extra_name_buf[4096]; ssize_t real_attr_len; ssize_t used = 0; if (fd != -1) { real_attr_len = real_flistxattr(fd, extra_name_buf, sizeof(extra_name_buf), options); } else { real_attr_len = real_listxattr(path, extra_name_buf, sizeof(extra_name_buf), options); } pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of FS xattr names, starting '%.*s'\n", (int) real_attr_len, (int) real_attr_len, extra_name_buf); /* we don't care why there aren't any */ if (real_attr_len < 1) { real_attr_len = 0; } pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf); if (result->result != RESULT_SUCCEED && real_attr_len < 1) { pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n"); errno = ENOATTR; return -1; } if (list) { pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n", (int) result->pathlen, (int) result->pathlen, result->path); if (size >= result->pathlen) { memcpy(list, result->path, result->pathlen); used = result->pathlen; } else { memcpy(list, result->path, size); used = size; errno = ERANGE; } if (real_attr_len > 0) { if ((ssize_t) size >= used + real_attr_len) { memcpy(list + used, extra_name_buf, real_attr_len); used += real_attr_len; } else { memcpy(list + used, extra_name_buf, size - used); used = size; errno = ERANGE; } } } else { used = real_attr_len + result->pathlen; } return used; } static int shared_removexattr(const char *path, int fd, const char *name, int options) { RC_AND_BUF if (!strncmp(name, "com.apple.", 10)) { if (fd != -1) { return real_fremovexattr(fd, name, options); } else { return real_removexattr(path, name, options); } } pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name); if (result->result != RESULT_SUCCEED) { /* docs say ENOATTR, but I don't have one */ errno = ENOENT; return -1; } return 0; } /* there's no fgetgrent_r or fgetpwent_r in Darwin */ #define PLENTY_LONG 2048 /* the original uid/gid code for Linux was written in terms of the * fget*ent_r() functions... which Darwin doesn't have. But wait! They're * actually pretty easy to implement. */ int pseudo_fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { char linebuf[PLENTY_LONG] = { 0 }; char *s, *t, *u; size_t max_members; char **members; size_t member = 0; long started_at = -1; gid_t gid; int error = ENOENT; size_t len; /* any early exit should set *gbufp to NULL */ if (gbufp) *gbufp = NULL; if (!gbuf || !fp || !buf) goto error_out; if (fp == pseudo_host_etc_group_file) { struct group *g; pseudo_antimagic(); g = getgrent(); pseudo_magic(); if (g) { char *s = linebuf; s += snprintf(linebuf, PLENTY_LONG, "%s:%s:%ld:", g->gr_name, g->gr_passwd, (long) g->gr_gid); if (g->gr_mem) { int i; for (i = 0; g->gr_mem[i]; ++i) { s += snprintf(s, PLENTY_LONG - (s - linebuf), "%s,", g->gr_mem[i]); } if (s[-1] == ',') --s; } strcpy(s, "\n"); } else { goto error_out; } } else { started_at = ftell(fp); if (started_at == -1) { goto error_out; } s = fgets(linebuf, PLENTY_LONG, fp); if (!s) { goto error_out; } } /* fgets will have stored a '\0' if there was no error; if there * was an error, though, linebuf was initialized to all zeroes so * the string is null-terminated anyway... */ len = strlen(linebuf); if (len > buflen) { error = ERANGE; goto error_out; } memcpy(buf, linebuf, len); /* round up to 8, hope for the best? */ len = len + 8 + (((unsigned long long) (buf + len)) % 8); members = (char **) (buf + len); if (len >= buflen) { error = ERANGE; goto error_out; } /* this is how many pointers we have room for... */ max_members = (buflen - len) / sizeof(*members); t = buf; /* yes, I can assume that Darwin has strsep() */ s = strsep(&t, ":"); if (!s) { goto error_out; } gbuf->gr_name = s; s = strsep(&t, ":"); if (!s) { goto error_out; } gbuf->gr_passwd = s; s = strsep(&t, ":"); if (!s) { goto error_out; } gid = (gid_t) strtol(s, &u, 10); /* should be a null byte, otherwise we didn't get a valid number */ if (*u) goto error_out; gbuf->gr_gid = gid; /* now, s points to a comma-separated list of members, which we * want to stash pointers to in 'members'. */ s = strsep(&t, ":"); t = s; while ((s = strsep(&t, ",")) != NULL) { if (*s) { if (member + 1 > max_members) { errno = ERANGE; goto error_out; } members[member++] = s; } } if (member + 1 > max_members) { errno = ERANGE; goto error_out; } members[member++] = NULL; *gbufp = gbuf; return 0; error_out: if (started_at != -1) fseek(fp, started_at, SEEK_SET); return error; return -1; } int pseudo_fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp) { char linebuf[PLENTY_LONG] = { 0 }; char *s, *t, *u; long started_at = -1; __darwin_time_t timestamp; uid_t uid; gid_t gid; int error = ENOENT; size_t len; /* any early exit should set *gbufp to NULL */ if (pbufp) *pbufp = NULL; if (!pbuf || !fp || !buf) goto error_out; if (fp == pseudo_host_etc_passwd_file) { struct passwd *p; pseudo_antimagic(); p = getpwent(); pseudo_magic(); if (p) { snprintf(linebuf, PLENTY_LONG, "%s:%s:%ld:%ld:%s:%ld:%ld:%s:%s:%s\n", p->pw_name, p->pw_passwd, (long) p->pw_uid, (long) p->pw_gid, p->pw_class, (long) p->pw_change, (long) p->pw_expire, p->pw_gecos, p->pw_dir, p->pw_shell); } else { goto error_out; } } else { started_at = ftell(fp); if (started_at == -1) { goto error_out; } s = fgets(linebuf, PLENTY_LONG, fp); if (!s) { goto error_out; } } /* fgets will have stored a '\0' if there was no error; if there * was an error, though, linebuf was initialized to all zeroes so * the string is null-terminated anyway... */ len = strlen(linebuf); if (len > buflen) { error = ERANGE; goto error_out; } if (linebuf[len - 1] == '\n') { linebuf[len - 1] = '\0'; --len; } memcpy(buf, linebuf, len); t = buf; /* yes, I can assume that Darwin has strsep() */ s = strsep(&t, ":"); if (!s) { goto error_out; } pbuf->pw_name = s; s = strsep(&t, ":"); if (!s) goto error_out; pbuf->pw_passwd = s; s = strsep(&t, ":"); if (!s) goto error_out; uid = (uid_t) strtol(s, &u, 10); /* should be a null byte, otherwise we didn't get a valid number */ if (*u) goto error_out; pbuf->pw_uid = uid; s = strsep(&t, ":"); if (!s) goto error_out; gid = (gid_t) strtol(s, &u, 10); /* should be a null byte, otherwise we didn't get a valid number */ if (*u) goto error_out; pbuf->pw_gid = gid; s = strsep(&t, ":"); if (!s) goto error_out; pbuf->pw_class = s; s = strsep(&t, ":"); if (!s) goto error_out; timestamp = (__darwin_time_t) strtol(s, &u, 10); /* should be a null byte, otherwise we didn't get a valid number */ if (*u) goto error_out; pbuf->pw_change = timestamp; timestamp = (__darwin_time_t) strtol(s, &u, 10); /* should be a null byte, otherwise we didn't get a valid number */ if (*u) goto error_out; pbuf->pw_expire = timestamp; s = strsep(&t, ":"); if (!s) goto error_out; pbuf->pw_gecos = s; s = strsep(&t, ":"); if (!s) goto error_out; pbuf->pw_dir = s; s = strsep(&t, ":"); if (!s) goto error_out; pbuf->pw_shell = s; *pbufp = pbuf; return 0; error_out: if (started_at != -1) fseek(fp, started_at, SEEK_SET); return error; return -1; } int pseudo_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { /* note that we don't wrap fgetpwent_r, since there's no path * references in it. */ if (!pseudo_pwd) { errno = ENOENT; return -1; } return pseudo_fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp); } int pseudo_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { /* note that we don't wrap fgetgrent_r, since there's no path * references in it. */ if (!pseudo_grp) { errno = ENOENT; return -1; } return pseudo_fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp); }