1 /* This file is part of GNU Pies.
2    Copyright (C) 2007-2020 Sergey Poznyakoff
3 
4    GNU Pies is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Pies is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <stdlib.h>
21 #include <string.h>
22 #include <pwd.h>
23 #include <grp.h>
24 #include <unistd.h>
25 #include <sysexits.h>
26 #include <stdbool.h>
27 #include "pies.h"
28 
29 struct grecs_list *
get_user_groups(struct grecs_list * init_list,const char * user)30 get_user_groups (struct grecs_list *init_list, const char *user)
31 {
32   int rc;
33   struct group *gr;
34   struct grecs_list *list;
35 
36   list = grecs_list_create ();
37 
38   if (init_list)
39     {
40       struct grecs_list_entry *ep;
41 
42       for (ep = init_list->head; ep; ep = ep->next)
43 	{
44 	  grecs_list_append (list, grecs_strdup ((char*)ep->data));
45 	}
46     }
47 
48   setgrent ();
49   for (rc = 0; rc == 0 && (gr = getgrent ());)
50     {
51       char **p;
52       for (p = gr->gr_mem; *p; p++)
53 	if (strcmp (*p, user) == 0)
54 	  {
55 	    grecs_list_append (list, grecs_strdup (gr->gr_name));
56 	    break;
57 	  }
58     }
59   endgrent ();
60   return list;
61 }
62 
63 /* Switch to the given UID/GID */
64 int
switch_to_privs(uid_t uid,gid_t gid,struct grecs_list * retain_groups)65 switch_to_privs (uid_t uid, gid_t gid, struct grecs_list *retain_groups)
66 {
67   int rc = 0;
68   gid_t *emptygidset;
69   size_t size = 1, j = 1;
70 
71   /* Create a list of supplementary groups */
72   size = 1 + (retain_groups ? grecs_list_size (retain_groups) : 0);
73   emptygidset = grecs_calloc (size, sizeof emptygidset[0]);
74   emptygidset[0] = gid ? gid : getegid ();
75 
76   if (retain_groups)
77     {
78       struct grecs_list_entry *ep;
79 
80       for (ep = retain_groups->head; ep; ep = ep->next)
81 	{
82 	  const char *grname = ep->data;
83 	  struct group *group = getgrnam (grname);
84 	  if (!group)
85 	    {
86 	      logmsg (LOG_ERR, _("unknown group: %s"), grname);
87 	      free (emptygidset);
88 	      return 1;
89 	    }
90 	  emptygidset[j++] = group->gr_gid;
91 	}
92     }
93 
94   /* Reset group permissions */
95   if (geteuid () == 0 && setgroups (j, emptygidset))
96     {
97       logmsg (LOG_ERR, _("setgroups(1, %lu) failed: %s"),
98 	      (unsigned long) emptygidset[0], strerror (errno));
99       rc = 1;
100     }
101   grecs_free (emptygidset);
102 
103   /* Switch to the user's gid. On some OSes the effective gid must
104      be reset first */
105 
106 #if defined(HAVE_SETEGID)
107   if ((rc = setegid (gid)) < 0)
108     logmsg (LOG_ERR, _("setegid(%lu) failed: %s"),
109 	      (unsigned long) gid, strerror (errno));
110 #elif defined(HAVE_SETREGID)
111   if ((rc = setregid (gid, gid)) < 0)
112     logmsg (LOG_ERR, _("setregid(%lu,%lu) failed: %s"),
113 	      (unsigned long) gid, (unsigned long) gid, strerror (errno));
114 #elif defined(HAVE_SETRESGID)
115   if ((rc = setresgid (gid, gid, gid)) < 0)
116     logmsg (LOG_ERR, _("setresgid(%lu,%lu,%lu) failed: %s"),
117 	      (unsigned long) gid,
118 	      (unsigned long) gid, (unsigned long) gid, strerror (errno));
119 #endif
120 
121   if (rc == 0 && gid != 0)
122     {
123       if ((rc = setgid (gid)) < 0 && getegid () != gid)
124 	logmsg (LOG_ERR, _("setgid(%lu) failed: %s"),
125 		  (unsigned long) gid, strerror (errno));
126       if (rc == 0 && getegid () != gid)
127 	{
128 	  logmsg (LOG_ERR, _("cannot set effective gid to %lu"),
129 		    (unsigned long) gid);
130 	  rc = 1;
131 	}
132     }
133 
134   /* Now reset uid */
135   if (rc == 0 && uid != 0)
136     {
137       uid_t euid;
138 
139       if (setuid (uid)
140 	  || geteuid () != uid
141 	  || (getuid () != uid && (geteuid () == 0 || getuid () == 0)))
142 	{
143 
144 #if defined(HAVE_SETREUID)
145 	  if (geteuid () != uid)
146 	    {
147 	      if (setreuid (uid, -1) < 0)
148 		{
149 		  logmsg (LOG_ERR, _("setreuid(%lu,-1) failed: %s"),
150 			    (unsigned long) uid, strerror (errno));
151 		  rc = 1;
152 		}
153 	      if (setuid (uid) < 0)
154 		{
155 		  logmsg (LOG_ERR, _("second setuid(%lu) failed: %s"),
156 			    (unsigned long) uid, strerror (errno));
157 		  rc = 1;
158 		}
159 	    }
160 	  else
161 #endif
162 	    {
163 	      logmsg (LOG_ERR, _("setuid(%lu) failed: %s"),
164 			(unsigned long) uid, strerror (errno));
165 	      rc = 1;
166 	    }
167 	}
168 
169       euid = geteuid ();
170       if (uid != 0 && setuid (0) == 0)
171 	{
172 	  logmsg (LOG_ERR, _("seteuid(0) succeeded when it should not"));
173 	  rc = 1;
174 	}
175       else if (uid != euid && setuid (euid) == 0)
176 	{
177 	  logmsg (LOG_ERR, _("cannot drop non-root setuid privileges"));
178 	  rc = 1;
179 	}
180 
181     }
182 
183   return rc;
184 }
185 
186 
187 void
pies_priv_setup(struct pies_privs * privs)188 pies_priv_setup (struct pies_privs *privs)
189 {
190   struct passwd *pw;
191   struct grecs_list *grplist = 0;
192 
193   if (!privs || !privs->user)
194     return;
195 
196   pw = getpwnam (privs->user);
197   if (!pw)
198     {
199       logmsg (LOG_ERR, _("no such user: %s"), privs->user);
200       exit (EX_CONFIG);
201     }
202 
203   if (privs->allgroups)
204     grplist = get_user_groups (privs->groups, privs->user);
205   if (switch_to_privs (pw->pw_uid, pw->pw_gid,
206 		       grplist ? grplist : privs->groups))
207     exit (EX_SOFTWARE);
208   if (grplist)
209     grecs_list_free (grplist);
210 }
211 
212 
213 void
pies_epriv_setup(struct pies_privs * privs)214 pies_epriv_setup (struct pies_privs *privs)
215 {
216   uid_t uid;
217   gid_t gid;
218 
219   if (privs)
220     {
221       struct passwd *pw;
222       if (!privs->user)
223 	return;
224 
225       pw = getpwnam (privs->user);
226       if (!pw)
227 	{
228 	  logmsg (LOG_ERR, _("no such user: %s"), privs->user);
229 	  exit (EX_CONFIG);
230 	}
231       uid = pw->pw_uid;
232       gid = pw->pw_gid;
233     }
234   else
235     {
236       uid = 0;
237       gid = 0;
238     }
239 
240   if (setegid (gid))
241     {
242       logmsg (LOG_ERR, _("cannot switch to EGID %lu: %s"),
243 		(unsigned long) gid, strerror (errno));
244       exit (EX_USAGE);
245     }
246   if (seteuid (uid))
247     {
248       logmsg (LOG_ERR, _("cannot switch to EUID %lu: %s"),
249 	      (unsigned long) uid, strerror (errno));
250       exit (EX_USAGE);
251     }
252 }
253 
254 int
pies_privs_cmp(struct pies_privs const * a,struct pies_privs const * b)255 pies_privs_cmp (struct pies_privs const *a, struct pies_privs const *b)
256 {
257   if (!a)
258     return !!b;
259   else if (!b)
260     return 1;
261   if (safe_strcmp (a->user, b->user))
262     return 1;
263   if (a->allgroups != b->allgroups)
264     return 1;
265   return grecs_list_compare (a->groups, b->groups);
266 }
267 
268 void
pies_privs_free(struct pies_privs * p)269 pies_privs_free (struct pies_privs *p)
270 {
271   grecs_free (p->user);
272   grecs_list_free (p->groups);
273 }
274 
275 
276