1 /++
2     Privilege dropping
3  +/
4 module socketplate.privdrop;
5 
6 import std.typecons : nullable, Nullable;
7 
8 @safe nothrow:
9 
10 version (Posix)
11 {
12     public import core.sys.posix.sys.types : uid_t, gid_t;
13 
14     ///
15     struct Privileges
16     {
17         Nullable!uid_t user; ///
18         Nullable!gid_t group; ///
19 
20         ///
21         static bool resolve(string username, string groupname, out Privileges result)
22         {
23             result = Privileges();
24 
25             uid_t uid;
26             gid_t gid;
27 
28             if (!resolveUsername(username, uid))
29                 return false;
30 
31             if (!resolveGroupname(groupname, gid))
32                 return false;
33 
34             result.user = uid;
35             result.group = gid;
36 
37             return true;
38         }
39     }
40 
41     ///
42     Privileges currentPrivileges() @nogc
43     {
44         import core.sys.posix.unistd;
45 
46         return Privileges(
47             nullable(getuid()),
48             nullable(getgid()),
49         );
50     }
51 
52     ///
53     bool resolveUsername(string username, out uid_t result) @trusted
54     {
55         import core.sys.posix.pwd;
56         import std.conv : to;
57         import std.string : fromStringz;
58 
59         bool usernameCouldBeUid = true;
60         uid_t usernameNumeric;
61         try
62             usernameNumeric = username.to!uid_t();
63         catch (Exception)
64             usernameCouldBeUid = false;
65 
66         scope (exit)
67             endpwent();
68 
69         for (passwd* db = getpwent(); db !is null; db = getpwent())
70         {
71             if (db.pw_name.fromStringz != username)
72             {
73                 if (!usernameCouldBeUid)
74                     continue;
75 
76                 if (db.pw_uid != usernameNumeric)
77                     continue;
78             }
79 
80             result = db.pw_uid;
81             return true;
82         }
83 
84         return false;
85     }
86 
87     ///
88     bool resolveGroupname(string groupname, out gid_t result) @trusted
89     {
90         import core.sys.posix.grp;
91         import std.conv : to;
92         import std.string : toStringz;
93 
94         group* g = getgrnam(groupname.toStringz);
95         if (g is null)
96         {
97             gid_t groupnameNumeric;
98             try
99                 groupnameNumeric = groupname.to!gid_t;
100             catch (Exception)
101                 return false;
102 
103             g = getgrgid(groupnameNumeric);
104 
105             if (g is null)
106                 return false;
107         }
108 
109         result = g.gr_gid;
110         return true;
111     }
112 
113     ///
114     bool dropPrivileges(Privileges privileges) @nogc
115     {
116         if (!privileges.group.isNull)
117             if (!dropGroup(privileges.group.get))
118                 return false;
119 
120         if (!privileges.user.isNull)
121             if (!dropUser(privileges.user.get))
122                 return false;
123 
124         return true;
125     }
126 
127     private @nogc
128     {
129         bool dropUser(uid_t uid) @trusted
130         {
131             import core.sys.posix.unistd : setuid;
132 
133             return (setuid(uid) == 0);
134         }
135 
136         bool dropGroup(gid_t uid) @trusted
137         {
138             import core.sys.posix.unistd : setgid;
139 
140             return (setgid(uid) == 0);
141         }
142     }
143 }