1 /*******************************************************************************
2 
3     Config extension to drop privileges.
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.util.app.ext.DropPrivilegesExt;
17 
18 
19 import ocean.core.array.Mutation;
20 import ocean.transition;
21 import ocean.text.util.StringC;
22 import ConfigFiller = ocean.util.config.ConfigFiller;
23 import ocean.util.app.ext.model.IConfigExtExtension;
24 import ocean.util.app.model.IApplicationExtension;
25 
26 import core.stdc.errno;
27 import core.stdc.string;
28 import core.sys.posix.grp;
29 import core.sys.posix.pwd;
30 import core.sys.posix.unistd;
31 
32 
33 /*******************************************************************************
34 
35     Config extension to drop privileges files.
36 
37     User and group must be specified in the configfile under the PERMISSION
38     section. If root starts the program, it will drop privileges after
39     reading the config. If it is started with the configured user,
40     nothing happens. If it is started as a different user, it will exit
41     with an error.
42 
43     Config example:
44     ----
45     [PERMISSIONS]
46     user = john
47     group = john
48     ----
49 
50 *******************************************************************************/
51 
52 class DropPrivilegesExt : IConfigExtExtension
53 {
54     static class Config
55     {
56         /***********************************************************************
57 
58             User to run as, mandatory setting
59 
60         ***********************************************************************/
61 
62         ConfigFiller.Required!(istring) user;
63 
64         /***********************************************************************
65 
66             Group to run as, mandatory setting
67 
68         ***********************************************************************/
69 
70         ConfigFiller.Required!(istring) group;
71     }
72 
73 
74     /***************************************************************************
75 
76         Extension order. This extension uses -5_000 because it should be
77         called pretty early, but after the ConfigExt extension.
78 
79         Returns:
80             the extension order
81 
82     ***************************************************************************/
83 
84     public override int order ( )
85     {
86         return -5000;
87     }
88 
89 
90     /***************************************************************************
91 
92         Function executed before the program runs.
93 
94         Params:
95             app = the application instance that will run
96             config = configuration instance to be filled
97 
98     ***************************************************************************/
99 
100     override void processConfig ( IApplication app, ConfigParser config )
101     {
102         auto conf = ConfigFiller.fill!(Config)("PERMISSIONS", config);
103 
104         if ( conf.group() == "root" ) throw new Exception("Group can not be root!");
105         if ( conf.user()  == "root" ) throw new Exception("User can not be root!");
106 
107         setGroup(conf.group());
108         setUser(conf.user());
109     }
110 
111 
112     /***************************************************************************
113 
114         Change user permissions to usr
115 
116         Params:
117             usr = User to become
118 
119     ***************************************************************************/
120 
121     private void setUser ( istring usr )
122     {
123         passwd* result;
124         passwd passwd_buf;
125         static mstring user_buf;
126         char[2048] buf;
127 
128         user_buf.copy(usr);
129         auto res = getpwnam_r(StringC.toCString(user_buf), &passwd_buf,
130                               buf.ptr, buf.length, &result);
131 
132         if ( result == null )
133         {
134             if ( res == 0 )
135             {
136                 throw new Exception("User " ~ usr ~ " not found!");
137             }
138             else
139             {
140                 char* err = strerror(res);
141                 auto msg = "Error while getting user " ~ usr ~
142                     ": " ~ StringC.toDString(err);
143                 throw new Exception(assumeUnique(msg));
144             }
145         }
146 
147         if ( result.pw_uid == geteuid() ) return;
148 
149         res = setuid(result.pw_uid);
150 
151         if ( res != 0 )
152         {
153             char* err = strerror(errno());
154             auto msg = "Failed to set process user id to " ~ usr
155                 ~ ": " ~ StringC.toDString(err);
156             throw new Exception(assumeUnique(msg));
157         }
158     }
159 
160 
161     /***************************************************************************
162 
163         Change group permissions to grp
164 
165         Params:
166             grp = Group to become
167 
168     ***************************************************************************/
169 
170     private void setGroup ( istring grp )
171     {
172         group* result;
173         group group_buf;
174         static mstring grp_buf;
175         char[2048] buf;
176 
177         grp_buf.copy(grp);
178         auto res = getgrnam_r(StringC.toCString(grp_buf), &group_buf,
179                               buf.ptr, buf.length, &result);
180 
181         if ( result == null )
182         {
183             if ( res == 0 )
184             {
185                 throw new Exception("Group " ~ grp ~ " not found!");
186             }
187             else
188             {
189                 char* err = strerror(res);
190                 auto msg = "Error while getting group " ~ grp ~
191                     ": " ~ StringC.toDString(err);
192                 throw new Exception(assumeUnique(msg));
193             }
194         }
195 
196         if ( result.gr_gid == getegid() ) return;
197 
198         res = setgid(result.gr_gid);
199 
200         if ( res != 0 )
201         {
202             char* err = strerror(errno());
203             auto msg = "Failed to set process user group to " ~ grp
204                 ~ ": " ~ StringC.toDString(err);
205             throw new Exception(assumeUnique(msg));
206         }
207     }
208 
209     /***************************************************************************
210 
211         Function executed before the configuration files are parsed.
212         Only present to satisfy the interface
213 
214         Params:
215             app = application instance
216             config = configuration parser
217 
218     ***************************************************************************/
219 
220     override void preParseConfig ( IApplication app, ConfigParser config ) {}
221 
222 
223     /***************************************************************************
224 
225         Function to filter the list of configuration files to parse.
226         Only present to satisfy the interface
227 
228         Params:
229             app = application instance
230             config = configuration parser
231             files = current list of configuration files to parse
232 
233         Returns:
234             new list of configuration files to parse
235 
236     ***************************************************************************/
237 
238     override istring[] filterConfigFiles ( IApplication app, ConfigParser config,
239                                   istring[] files ) { return files; }
240 }