chiark / gitweb /
checkpath.c: Hoist the `buf' used for link targets for reuse.
[checkpath] / checkpath.c
index 2047800448dfd6355481a725fd6848e83a6346b6..d874246025f69ed4b69fe14259226569304a88fa 100644 (file)
@@ -43,6 +43,7 @@
 
 #include <mLib/alloc.h>
 #include <mLib/dstr.h>
+#include <mLib/macros.h>
 
 #include "checkpath.h"
 
@@ -58,16 +59,14 @@ struct elt {
   struct elt *e_link;                  /* Pointer to the next one along */
   size_t e_offset;                     /* Offset of name in path string */
   unsigned e_flags;                    /* Various useful flags */
+#define EF_STICKY 1u                   /*   Directory has sticky bit set */
   char e_name[1];                      /* Name of the directory */
 };
 
-#define f_sticky 1u                    /* Directory has sticky bit set */
-
-#define f_last 1u                      /* This is the final item to check */
 
 /*----- Static variables --------------------------------------------------*/
 
-static struct elt rootnode = { 0, 0, 0 }; /* Root of the list */
+static const struct elt rootnode = { 0, 0, 0 }; /* Root of the list */
 static struct elt *sp;                 /* Stack pointer for list */
 static dstr d = DSTR_INIT;             /* Current path string */
 
@@ -138,7 +137,7 @@ static void pop(void)
     struct elt *e = sp->e_link;
     d.len = sp->e_offset;
     DPUTZ(&d);
-    sp = e;
+    xfree(sp); sp = e;
   }
 }
 
@@ -271,13 +270,17 @@ static void report(const struct checkpath *cp, unsigned what, int verbose,
  * Arguments:  @const char *p@ = name of directory to check
  *             @struct stat *st@ = pointer to @stat@(2) block for it
  *             @const struct checkpath *cp@ = pointer to caller parameters
- *             @unsigned f@ = various flags
+ *             @unsigned f@ = various flags (@SF_...@)
  *
  * Returns:    Zero if everything's OK, else bitmask of problems.
  *
  * Use:                Performs the main load of sanity-checking on a directory.
+ *             If @SF_LAST@ is not set then sticky directories are always
+ *             acceptable.
  */
 
+#define SF_LAST 1u                     /* This is the final item to check */
+
 static unsigned sanity(const char *p, struct stat *st,
                       const struct checkpath *cp, unsigned f)
 {
@@ -287,13 +290,13 @@ static unsigned sanity(const char *p, struct stat *st,
   unsigned b;
 
   if (S_ISDIR(st->st_mode) &&
-      (!(f & f_last) || (cp->cp_what & CP_STICKYOK)))
-    stickyok = 01000;
+      (!(f & SF_LAST) || (cp->cp_what & CP_STICKYOK)))
+    stickyok = S_ISVTX;
 
   /* --- Check for world-writability --- */
 
   if ((cp->cp_what & CP_WRWORLD) &&
-      (st->st_mode & (0002 | stickyok)) == 0002) {
+      (st->st_mode & (S_IWOTH | stickyok)) == S_IWOTH) {
     bad |= CP_WRWORLD;
     report(cp, CP_WRWORLD, 1, p, "** world writable **");
   }
@@ -301,7 +304,7 @@ static unsigned sanity(const char *p, struct stat *st,
   /* --- Check for group-writability --- */
 
   if ((cp->cp_what & (CP_WRGRP | CP_WROTHGRP)) &&
-      (st->st_mode & (0020 | stickyok)) == 0020) {
+      (st->st_mode & (S_IWGRP | stickyok)) == S_IWGRP) {
     b = CP_WRGRP;
 
     if (cp->cp_what & CP_WROTHGRP) {
@@ -349,10 +352,11 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
   struct elt *e, *ee;
   struct stat st;
   unsigned bad = 0;
+  dstr buf = DSTR_INIT;
 
   /* --- Initialize stack pointer and path string --- */
 
-  sp = &rootnode;
+  sp = (/*unconst*/ struct elt *)&rootnode;
   dstr_destroy(&d);
 
   /* --- Try to find the current directory --- */
@@ -386,7 +390,7 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
     /* --- Strip off simple `.' elements --- */
 
     if (strcmp(ee->e_name, ".") == 0) {
-      free(ee);
+      xfree(ee);
       ee = e;
       continue;
     }
@@ -395,7 +399,7 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
 
     else if (strcmp(ee->e_name, "..") == 0) {
       pop();
-      free(ee);
+      xfree(ee);
       ee = e;
       continue;
     }
@@ -416,11 +420,11 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
     /* --- Handle symbolic links specially --- */
 
     if (S_ISLNK(st.st_mode)) {
-      dstr buf = DSTR_INIT;
       int i;
 
       /* --- Resolve the link --- */
 
+      dstr_reset(&buf);
       dstr_ensure(&buf, st.st_size + 1);
       if ((i = readlink(d.buf, buf.buf, buf.sz)) < 0) {
        report(cp, CP_ERROR, 0, d.buf, "can't readlink: %e");
@@ -438,7 +442,7 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
        */
 
       if ((cp->cp_what & CP_WROTHUSR) &&
-         (sp->e_link->e_flags & f_sticky) &&
+         (sp->e_link->e_flags & EF_STICKY) &&
          st.st_uid != cp->cp_uid && st.st_uid != 0) {
        bad |= CP_WROTHUSR;
        report(cp, CP_WROTHUSR, 1, d.buf,
@@ -452,17 +456,16 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
       else
        pop();
       ee = splitpath(buf.buf, ee);
-      dstr_destroy(&buf);
       continue;
     }
 
     /* --- Run the sanity check on this path element --- */
 
-    bad |= sanity(d.buf, &st, cp, ee ? 0 : f_last);
+    bad |= sanity(d.buf, &st, cp, ee ? 0 : SF_LAST);
 
     if (S_ISDIR(st.st_mode)) {
-      if (st.st_mode & 01000)
-       sp->e_flags |= f_sticky;
+      if (st.st_mode & S_ISVTX)
+       sp->e_flags |= EF_STICKY;
       report(cp, CP_REPORT, 4, d.buf, "directory");
       continue;
     }
@@ -479,39 +482,105 @@ unsigned checkpath(const char *p, const struct checkpath *cp)
       report(cp, CP_ERROR, 0, 0, "junk left over after reaching leaf");
     while (ee) {
       e = ee->e_link;
-      free(ee);
+      xfree(ee);
       ee = e;
     }
   }
 
   popall();
+  dstr_destroy(&buf);
   return (bad);
 }
 
-/* --- @checkpath_setids@ --- *
+/* --- @checkpath_addgid@ --- *
  *
  * Arguments:  @struct checkpath *cp@ = pointer to block to fill in
+ *             @gid_t g@ = group id to add
  *
- * Returns:    ---
+ * Returns:    Zero if successful, nonzero if the array is full.
  *
- * Use:                Fills in the user ids and things in the structure.
+ * Use:                Adds the group @g@ to the structure.
  */
 
-void checkpath_setids(struct checkpath *cp)
+int checkpath_addgid(struct checkpath *cp, gid_t g)
 {
-  int n, i;
-  gid_t g = getgid();
+  int i;
 
-  cp->cp_uid = getuid();
-  n = getgroups(sizeof(cp->cp_gid) / sizeof(cp->cp_gid[0]), cp->cp_gid);
+  for (i = 0; i < cp->cp_gids; i++) {
+    if (cp->cp_gid[i] == g)
+      return (0);
+  }
+  if (cp->cp_gids >= N(cp->cp_gid))
+    return (-1);
+  cp->cp_gid[cp->cp_gids++] = g;
+  return (0);
+}
+
+/* --- @checkpath_setuid@ --- *
+ *
+ * Arguments:  @struct checkpath *cp@ = pointer to block to fill in
+ *
+ * Returns:    ---
+ *
+ * Use:                Fills in the @cp_uid@ slot of the structure with the real uid
+ *             of the current process.
+ */
+
+void checkpath_setuid(struct checkpath *cp) { cp->cp_uid = getuid(); }
+
+/* --- @checkpath_setgid@ --- *
+ *
+ * Arguments:  @struct checkpath *cp@ = pointer to block to fill in
+ *
+ * Returns:    Zero if successful, nonzero if the array is full.
+ *
+ * Use:                Adds the real gid of the current process to the @cp_gid@
+ *             array.
+ */
 
+int checkpath_setgid(struct checkpath *cp)
+  { return (checkpath_addgid(cp, getgid())); }
+
+/* --- @checkpath_setgroups@ --- *
+ *
+ * Arguments:  @struct checkpath *cp@ = pointer to block to fill in
+ *
+ * Returns:    Zero if successful, nonzero if the array is full.
+ *
+ * Use:                Adds the current process's supplementary groups to the
+ *             @cp_gid@ table.
+ */
+
+int checkpath_setgroups(struct checkpath *cp)
+{
+  int i, n;
+  gid_t gg[NGROUPS_MAX];
+
+  n = getgroups(N(gg), gg);
   for (i = 0; i < n; i++) {
-    if (cp->cp_gid[i] == g)
-      goto gid_ok;
+    if (checkpath_addgid(cp, gg[i]))
+      return (-1);
   }
-  cp->cp_gid[n++] = g;
-gid_ok:
-  cp->cp_gids = n;
+  return (0);
+}
+
+/* --- @checkpath_setids@ --- *
+ *
+ * Arguments:  @struct checkpath *cp@ = pointer to block to fill in
+ *
+ * Returns:    ---
+ *
+ * Use:                Fills in the user ids and things in the structure.  This is
+ *             equivalent to setting @cp_gids = 0@ and then calling
+ *             @_setuid@, @_setgid@ and @_setgroups@.  It can't fail.
+ */
+
+void checkpath_setids(struct checkpath *cp)
+{
+  cp->cp_gids = 0;
+  checkpath_setuid(cp);
+  checkpath_setgid(cp);
+  checkpath_setgroups(cp);
 }
 
 /*----- That's all, folks -------------------------------------------------*/