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