Switch to side-by-side view

--- a/src/upmpdutils.cxx
+++ b/src/upmpdutils.cxx
@@ -23,33 +23,37 @@
 
 #include "upmpdutils.hxx"
 
-#include <errno.h>                      // for errno
-#include <fcntl.h>                      // for open, O_RDONLY, O_CREAT, etc
-#include <math.h>                       // for exp10, floor, log10, sqrt
-#include <pwd.h>                        // for getpwnam, getpwuid, passwd
-#include <regex.h>                      // for regmatch_t, regfree, etc
-#include <stdio.h>                      // for sprintf
-#include <stdlib.h>                     // for getenv, strtol
-#include <string.h>                     // for strerror, strerror_r
-#include <sys/file.h>                   // for flock, LOCK_EX, LOCK_NB
-#include <sys/stat.h>                   // for fstat, mkdir, stat
-#include <unistd.h>                     // for close, pid_t, ftruncate, etc
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <pwd.h>
+#include <grp.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
 
 #ifndef O_STREAMING
 #define O_STREAMING 0
 #endif
-#include <fstream>                      // for operator<<, basic_ostream, etc
-#include <sstream>                      // for ostringstream
-#include <utility>                      // for pair
-#include <vector>                       // for vector
-
-#include "libupnpp/log.hxx"             // for LOGERR
-#include "libupnpp/soaphelp.hxx"        // for xmlQuote
-#include "libupnpp/upnpavutils.hxx"     // for upnpduration
+#include <fstream>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "libupnpp/log.hxx"
+#include "libupnpp/soaphelp.hxx"
+#include "libupnpp/upnpavutils.hxx"
 #include "libupnpp/control/cdircontent.hxx"
 
-#include "mpdcli.hxx"                   // for UpSong
+#include "mpdcli.hxx"
 #include "smallut.h"
+
 
 using namespace std;
 using namespace UPnPP;
@@ -303,3 +307,91 @@
     regfree(&expr);
     return out;
 }
+
+// Make sure that the configuration file is readable by user upmpdcli.
+// This is only called if we are started as root, before switching
+// users.  We do the minimum change: set the user read bit if the
+// file belongs to it, else set the group read bit if it belongs to
+// one of the user groups, else, have to chrgp it.
+bool ensureconfreadable(const char *fn, const char *user, uid_t uid,
+                        gid_t gid)
+{
+    LOGDEB1("ensureconfreadable: fn " << fn << " user " << user << " uid " <<
+           uid << " gid " << gid << endl);
+
+    struct stat st;
+    if (stat(fn, &st) < 0) {
+        LOGERR("ensureconfreadable: can't stat " << fn << " errno " <<
+               errno << endl);
+        return false;
+    }
+    if ((st.st_mode & S_IROTH)) {
+        // World-readable, we're done
+        LOGDEB1("ensureconfreadable: file is world-readable\n");
+        return true;
+    }
+
+    if (st.st_uid == uid) {
+        LOGDEB1("ensureconfreadable: file belongs to user\n");
+        // File belongs to user. Make sure that "owner read" is
+        // set. Don't complicate things. "no owner read" does not make
+        // sense anyway (can always chmod).
+        if (!(st.st_mode & S_IRUSR)) {
+            if (chmod(fn, st.st_mode|S_IRUSR) < 0) {
+                LOGERR("ensureconfreadable: chmod(st.st_mode|S_IRUSR) failed."
+                       " errno " << errno << endl);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Deal with groups.
+    // Note that 1000 is arbitrary, we should call with 0 to retrieve
+    // the actual size and allocate accordingly then recall
+    int ngroups{1000};
+    gid_t groups[1000];
+    if (getgrouplist(user, gid, groups, &ngroups) < 0) {
+        LOGERR("ensureconfreadable: getgrouplist failed for " << user << endl);
+        return false;
+    }
+
+    // Check if file belongs to one of our groups. Make it
+    // group-readable if so. Determine our lowest group while we're at
+    // it.
+    unsigned int mingroup{UINT_MAX};
+    for (int i = 0;i < ngroups; i++) {
+        if (groups[i] < mingroup) {
+            mingroup = groups[i];
+        }
+        if (st.st_gid == groups[i]) {
+            if (!(st.st_mode & S_IRGRP)) {
+                if (chmod(fn, st.st_mode|S_IRGRP) < 0) {
+                    LOGERR("ensureconfreadable: chmod(st.st_mode|S_IRGRP) "
+                           "failed.  errno " << errno << endl);
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    // File does not belong to any of our groups. We already checked
+    // that "other read" was not set. Have to change the file
+    // group. Note that we could also add the file group to our group
+    // list, but this seems like a worse idea (making us a setgid
+    // executable in practise).
+    if (chown(fn, (uid_t)-1, mingroup) < 0) {
+        LOGERR("ensureconfreadable: chown(" << fn << "-1, " << mingroup <<") "
+               "failed. errno " << errno << endl);
+        return false;
+    }
+    if (!(st.st_mode & S_IRGRP)) {
+        if (chmod(fn, st.st_mode|S_IRGRP) < 0) {
+            LOGERR("ensureconfreadable: chmod(st.st_mode|S_IRGRP) "
+                   "failed.  errno " << errno << endl);
+            return false;
+        }
+    }
+    return true;
+}