+ struct group *grp;
+ if (!name || !*name)
+ return 0;
+ if (!(grp = getgrnam(name)))
+ return 0;
+ *gid_p = grp->gr_gid;
+ return 1;
+}
+
+/** Lock a byte range in a open file */
+int lock_range(int fd, int offset, int len)
+{
+ struct flock lock;
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = offset;
+ lock.l_len = len;
+ lock.l_pid = 0;
+
+ return fcntl(fd,F_SETLK,&lock) == 0;
+}
+
+#define ENSURE_MEMSPACE(buf, type, sz, req) \
+ if ((req) > sz && !(buf = realloc_array(buf, type, sz = MAX(sz * 2, req)))) \
+ out_of_memory("glob_expand")
+
+static inline void call_glob_match(const char *name, int len, int from_glob,
+ char *arg, int abpos, int fbpos);
+
+static struct glob_data {
+ char *arg_buf, *filt_buf, **argv;
+ int absize, fbsize, maxargs, argc;
+} glob;
+
+static void glob_match(char *arg, int abpos, int fbpos)
+{
+ int len;
+ char *slash;
+
+ while (*arg == '.' && arg[1] == '/') {
+ if (fbpos < 0) {
+ ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, glob.absize);
+ memcpy(glob.filt_buf, glob.arg_buf, abpos + 1);
+ fbpos = abpos;
+ }
+ ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + 3);
+ glob.arg_buf[abpos++] = *arg++;
+ glob.arg_buf[abpos++] = *arg++;
+ glob.arg_buf[abpos] = '\0';
+ }
+ if ((slash = strchr(arg, '/')) != NULL) {
+ *slash = '\0';
+ len = slash - arg;
+ } else
+ len = strlen(arg);
+ if (strpbrk(arg, "*?[")) {
+ struct dirent *di;
+ DIR *d;
+
+ if (!(d = opendir(abpos ? glob.arg_buf : ".")))
+ return;
+ while ((di = readdir(d)) != NULL) {
+ char *dname = d_name(di);
+ if (dname[0] == '.' && (dname[1] == '\0'
+ || (dname[1] == '.' && dname[2] == '\0')))
+ continue;
+ if (!wildmatch(arg, dname))
+ continue;
+ call_glob_match(dname, strlen(dname), 1,
+ slash ? arg + len + 1 : NULL,
+ abpos, fbpos);
+ }
+ closedir(d);
+ } else {
+ call_glob_match(arg, len, 0,
+ slash ? arg + len + 1 : NULL,
+ abpos, fbpos);
+ }
+ if (slash)
+ *slash = '/';
+}
+
+static inline void call_glob_match(const char *name, int len, int from_glob,
+ char *arg, int abpos, int fbpos)
+{
+ char *use_buf;
+
+ ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2);
+ memcpy(glob.arg_buf + abpos, name, len);
+ abpos += len;
+ glob.arg_buf[abpos] = '\0';
+
+ if (fbpos >= 0) {
+ ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2);
+ memcpy(glob.filt_buf + fbpos, name, len);
+ fbpos += len;
+ glob.filt_buf[fbpos] = '\0';
+ use_buf = glob.filt_buf;
+ } else
+ use_buf = glob.arg_buf;
+
+ if (from_glob || (arg && len)) {
+ STRUCT_STAT st;
+ int is_dir;
+
+ if (do_stat(glob.arg_buf, &st) != 0)
+ return;
+ is_dir = S_ISDIR(st.st_mode) != 0;
+ if (arg && !is_dir)
+ return;
+
+ if (daemon_filter_list.head
+ && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0)
+ return;
+ }
+
+ if (arg) {
+ glob.arg_buf[abpos++] = '/';
+ glob.arg_buf[abpos] = '\0';
+ if (fbpos >= 0) {
+ glob.filt_buf[fbpos++] = '/';
+ glob.filt_buf[fbpos] = '\0';
+ }
+ glob_match(arg, abpos, fbpos);
+ } else {
+ ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
+ if (!(glob.argv[glob.argc++] = strdup(glob.arg_buf)))
+ out_of_memory("glob_match");
+ }
+}
+
+/* This routine performs wild-card expansion of the pathname in "arg". Any
+ * daemon-excluded files/dirs will not be matched by the wildcards. Returns 0
+ * if a wild-card string is the only returned item (due to matching nothing). */
+int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
+{
+ int ret, save_argc;
+ char *s;
+
+ if (!arg) {
+ if (glob.filt_buf)
+ free(glob.filt_buf);
+ free(glob.arg_buf);
+ memset(&glob, 0, sizeof glob);
+ return -1;
+ }
+
+ if (sanitize_paths)
+ s = sanitize_path(NULL, arg, "", 0, SP_KEEP_DOT_DIRS);
+ else {
+ s = strdup(arg);
+ if (!s)
+ out_of_memory("glob_expand");
+ clean_fname(s, CFN_KEEP_DOT_DIRS
+ | CFN_KEEP_TRAILING_SLASH
+ | CFN_COLLAPSE_DOT_DOT_DIRS);
+ }
+
+ ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, MAXPATHLEN);
+ *glob.arg_buf = '\0';
+
+ glob.argc = save_argc = *argc_p;
+ glob.argv = *argv_p;
+ glob.maxargs = *maxargs_p;
+
+ ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, 100);
+
+ glob_match(s, 0, -1);
+
+ /* The arg didn't match anything, so add the failed arg to the list. */
+ if (glob.argc == save_argc) {
+ ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
+ glob.argv[glob.argc++] = s;
+ ret = 0;
+ } else {
+ free(s);
+ ret = 1;
+ }
+
+ *maxargs_p = glob.maxargs;
+ *argv_p = glob.argv;
+ *argc_p = glob.argc;
+
+ return ret;
+}
+
+/* This routine is only used in daemon mode. */
+void glob_expand_module(char *base1, char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
+{
+ char *p, *s;
+ char *base = base1;
+ int base_len = strlen(base);
+
+ if (!arg || !*arg)
+ return;
+
+ if (strncmp(arg, base, base_len) == 0)
+ arg += base_len;
+
+ if (!(arg = strdup(arg)))
+ out_of_memory("glob_expand_module");
+
+ if (asprintf(&base," %s/", base1) <= 0)
+ out_of_memory("glob_expand_module");
+ base_len++;
+
+ for (s = arg; *s; s = p + base_len) {
+ if ((p = strstr(s, base)) != NULL)
+ *p = '\0'; /* split it at this point */
+ glob_expand(s, argv_p, argc_p, maxargs_p);
+ if (!p)
+ break;
+ }
+
+ free(arg);
+ free(base);
+}
+
+/**
+ * Convert a string to lower case
+ **/
+void strlower(char *s)
+{
+ while (*s) {
+ if (isUpper(s))
+ *s = toLower(s);
+ s++;
+ }
+}
+
+/* Join strings p1 & p2 into "dest" with a guaranteed '/' between them. (If
+ * p1 ends with a '/', no extra '/' is inserted.) Returns the length of both
+ * strings + 1 (if '/' was inserted), regardless of whether the null-terminated
+ * string fits into destsize. */
+size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2)
+{
+ size_t len = strlcpy(dest, p1, destsize);
+ if (len < destsize - 1) {
+ if (!len || dest[len-1] != '/')
+ dest[len++] = '/';
+ if (len < destsize - 1)
+ len += strlcpy(dest + len, p2, destsize - len);
+ else {
+ dest[len] = '\0';
+ len += strlen(p2);
+ }
+ }
+ else
+ len += strlen(p2) + 1; /* Assume we'd insert a '/'. */
+ return len;
+}
+
+/* Join any number of strings together, putting them in "dest". The return
+ * value is the length of all the strings, regardless of whether the null-
+ * terminated whole fits in destsize. Your list of string pointers must end
+ * with a NULL to indicate the end of the list. */
+size_t stringjoin(char *dest, size_t destsize, ...)
+{
+ va_list ap;
+ size_t len, ret = 0;
+ const char *src;
+
+ va_start(ap, destsize);
+ while (1) {
+ if (!(src = va_arg(ap, const char *)))
+ break;
+ len = strlen(src);
+ ret += len;
+ if (destsize > 1) {
+ if (len >= destsize)
+ len = destsize - 1;
+ memcpy(dest, src, len);
+ destsize -= len;
+ dest += len;
+ }
+ }
+ *dest = '\0';
+ va_end(ap);
+
+ return ret;
+}
+
+int count_dir_elements(const char *p)
+{
+ int cnt = 0, new_component = 1;
+ while (*p) {
+ if (*p++ == '/')
+ new_component = (*p != '.' || (p[1] != '/' && p[1] != '\0'));
+ else if (new_component) {
+ new_component = 0;
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+/* Turns multiple adjacent slashes into a single slash, drops all leading or
+ * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged. Will also drop
+ * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes
+ * a trailing slash (perhaps after removing the aforementioned dot) unless
+ * CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements
+ * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the
+ * resulting name would be empty, returns ".". */
+unsigned int clean_fname(char *name, int flags)
+{
+ char *limit = name - 1, *t = name, *f = name;
+ int anchored;
+
+ if (!name)
+ return 0;
+
+ if ((anchored = *f == '/') != 0)
+ *t++ = *f++;
+ else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') {
+ *t++ = *f++;
+ *t++ = *f++;
+ }
+ while (*f) {
+ /* discard extra slashes */
+ if (*f == '/') {
+ f++;
+ continue;
+ }
+ if (*f == '.') {
+ /* discard interior "." dirs */
+ if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) {
+ f += 2;
+ continue;
+ }
+ if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR)
+ break;
+ /* collapse ".." dirs */
+ if (flags & CFN_COLLAPSE_DOT_DOT_DIRS
+ && f[1] == '.' && (f[2] == '/' || !f[2])) {
+ char *s = t - 1;
+ if (s == name && anchored) {
+ f += 2;
+ continue;
+ }
+ while (s > limit && *--s != '/') {}
+ if (s != t - 1 && (s < name || *s == '/')) {
+ t = s + 1;
+ f += 2;
+ continue;
+ }
+ limit = t + 2;
+ }
+ }
+ while (*f && (*t++ = *f++) != '/') {}
+ }
+
+ if (t > name+anchored && t[-1] == '/' && !(flags & CFN_KEEP_TRAILING_SLASH))
+ t--;
+ if (t == name)
+ *t++ = '.';
+ *t = '\0';
+
+ return t - name;
+}
+
+/* Make path appear as if a chroot had occurred. This handles a leading
+ * "/" (either removing it or expanding it) and any leading or embedded
+ * ".." components that attempt to escape past the module's top dir.
+ *
+ * If dest is NULL, a buffer is allocated to hold the result. It is legal
+ * to call with the dest and the path (p) pointing to the same buffer, but
+ * rootdir will be ignored to avoid expansion of the string.
+ *
+ * The rootdir string contains a value to use in place of a leading slash.
+ * Specify NULL to get the default of "module_dir".
+ *
+ * The depth var is a count of how many '..'s to allow at the start of the
+ * path.
+ *
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences:
+ *
+ * Turns multiple adjacent slashes into a single slash, gets rid of "." dir
+ * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and
+ * ALWAYS collapses ".." elements (except for those at the start of the
+ * string up to "depth" deep). If the resulting name would be empty,
+ * change it into a ".". */
+char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth,
+ int flags)
+{
+ char *start, *sanp;
+ int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS);
+
+ if (dest != p) {
+ int plen = strlen(p);
+ if (*p == '/') {
+ if (!rootdir)
+ rootdir = module_dir;
+ rlen = strlen(rootdir);
+ depth = 0;
+ p++;
+ }
+ if (dest) {
+ if (rlen + plen + 1 >= MAXPATHLEN)
+ return NULL;
+ } else if (!(dest = new_array(char, rlen + plen + 1)))
+ out_of_memory("sanitize_path");
+ if (rlen) {
+ memcpy(dest, rootdir, rlen);
+ if (rlen > 1)
+ dest[rlen++] = '/';
+ }
+ }
+
+ if (drop_dot_dirs) {
+ while (*p == '.' && p[1] == '/')
+ p += 2;
+ }
+
+ start = sanp = dest + rlen;
+ /* This loop iterates once per filename component in p, pointing at
+ * the start of the name (past any prior slash) for each iteration. */
+ while (*p) {
+ /* discard leading or extra slashes */
+ if (*p == '/') {
+ p++;
+ continue;
+ }
+ if (drop_dot_dirs) {
+ if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
+ /* skip "." component */
+ p++;
+ continue;
+ }
+ }
+ if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
+ /* ".." component followed by slash or end */
+ if (depth <= 0 || sanp != start) {
+ p += 2;
+ if (sanp != start) {
+ /* back up sanp one level */
+ --sanp; /* now pointing at slash */
+ while (sanp > start && sanp[-1] != '/')
+ sanp--;
+ }
+ continue;
+ }
+ /* allow depth levels of .. at the beginning */
+ depth--;
+ /* move the virtual beginning to leave the .. alone */
+ start = sanp + 3;
+ }
+ /* copy one component through next slash */
+ while (*p && (*sanp++ = *p++) != '/') {}
+ }
+ if (sanp == dest) {
+ /* ended up with nothing, so put in "." component */
+ *sanp++ = '.';
+ }
+ *sanp = '\0';
+
+ return dest;
+}
+
+/* Like chdir(), but it keeps track of the current directory (in the
+ * global "curr_dir"), and ensures that the path size doesn't overflow.
+ * Also cleans the path using the clean_fname() function. */
+int change_dir(const char *dir, int set_path_only)
+{
+ static int initialised;
+ unsigned int len;
+
+ if (!initialised) {
+ initialised = 1;
+ getcwd(curr_dir, sizeof curr_dir - 1);
+ curr_dir_len = strlen(curr_dir);
+ }
+
+ if (!dir) /* this call was probably just to initialize */
+ return 0;
+
+ len = strlen(dir);
+ if (len == 1 && *dir == '.')