Use the new HAVE_GETGROUPS define.
[rsync/rsync.git] / uidlist.c
1 /* 
2    Copyright (C) Andrew Tridgell 1996
3    Copyright (C) Paul Mackerras 1996
4    
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14    
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 /* handle the mapping of uid/gid and user/group names between systems.
21    If the source username/group does not exist on the target then use
22    the numeric ids. Never do any mapping for uid=0 or gid=0 as these
23    are special.
24 */
25
26 #include "rsync.h"
27
28 #ifdef HAVE_GETGROUPS
29 # if !defined(GETGROUPS_T)
30 #  define GETGROUPS_T gid_t
31 # endif
32 # ifndef NGROUPS_MAX
33 /* It ought to be defined, but just in case. */
34 #  define NGROUPS_MAX 32
35 # endif
36 #endif
37
38 extern int verbose;
39 extern int preserve_uid;
40 extern int preserve_gid;
41 extern int numeric_ids;
42 extern int am_root;
43
44 struct idlist {
45         struct idlist *next;
46         int id, id2;
47         char *name;
48 };
49
50 static struct idlist *uidlist;
51 static struct idlist *gidlist;
52
53 static struct idlist *add_list(int id, char *name)
54 {
55         struct idlist *list = new(struct idlist);
56         if (!list) out_of_memory("add_list");
57         list->next = NULL;
58         list->name = strdup(name);
59         if (!list->name) out_of_memory("add_list");
60         list->id = (int)id;
61         return list;
62 }
63
64
65
66 /* turn a uid into a user name */
67 static char *uid_to_name(uid_t uid)
68 {
69         struct passwd *pass = getpwuid(uid);
70         if (pass) return(pass->pw_name);
71         return NULL;
72 }
73
74 /* turn a gid into a group name */
75 static char *gid_to_name(gid_t gid)
76 {
77         struct group *grp = getgrgid(gid);
78         if (grp) return(grp->gr_name);
79         return NULL;
80 }
81
82 static int map_uid(int id, char *name)
83 {
84         uid_t uid;
85         if (uid != 0 && name_to_uid(name, &uid))
86                 return uid;
87         return id;
88 }
89
90 static int map_gid(int id, char *name)
91 {
92         gid_t gid;
93         if (gid != 0 && name_to_gid(name, &gid))
94                 return gid;
95         return id;
96 }
97
98 /* this function is a definate candidate for a faster algorithm */
99 static uid_t match_uid(uid_t uid)
100 {
101         static uid_t last_in, last_out;
102         struct idlist *list = uidlist;
103
104         if (uid == last_in)
105                 return last_out;
106
107         last_in = uid;
108
109         while (list) {
110                 if (list->id == (int)uid) {
111                         last_out = (uid_t)list->id2;
112                         return last_out;
113                 }
114                 list = list->next;
115         }
116         
117         last_out = uid;
118         return last_out;
119 }
120
121 static int is_in_group(gid_t gid)
122 {
123 #ifdef HAVE_GETGROUPS
124         static gid_t last_in = GID_NONE, last_out;
125         static int ngroups = -2;
126         static GETGROUPS_T *gidset;
127         int n;
128
129         if (gid == last_in)
130                 return last_out;
131         if (ngroups < -1) {
132                 gid_t mygid = MY_GID();
133                 ngroups = getgroups(0, 0);
134                 /* If that didn't work, perhaps 0 isn't treated specially? */
135                 if (ngroups <= 0)
136                         ngroups = NGROUPS_MAX;
137                 gidset = new_array(GETGROUPS_T, ngroups+1);
138                 if (ngroups > 0)
139                         ngroups = getgroups(ngroups, gidset);
140                 /* The default gid might not be in the list on some systems. */
141                 for (n = 0; n < ngroups; n++) {
142                         if (gidset[n] == mygid)
143                                 break;
144                 }
145                 if (n == ngroups)
146                         gidset[ngroups++] = mygid;
147                 if (verbose > 3) {
148                         char gidbuf[NGROUPS_MAX*16+32];
149                         int pos;
150                         sprintf(gidbuf, "process has %d gid%s: ",
151                             ngroups, ngroups == 1? "" : "s");
152                         pos = strlen(gidbuf);
153                         for (n = 0; n < ngroups; n++) {
154                                 sprintf(gidbuf+pos, " %ld", (long)gidset[n]);
155                                 pos += strlen(gidbuf+pos);
156                         }
157                         rprintf(FINFO, "%s\n", gidbuf);
158                 }
159         }
160
161         last_in = gid;
162         for (n = 0; n < ngroups; n++) {
163                 if (gidset[n] == gid)
164                         return last_out = 1;
165         }
166         return last_out = 0;
167
168 #else
169         static gid_t mygid = GID_NONE;
170         if (mygid == GID_NONE) {
171                 mygid = MY_GID();
172                 if (verbose > 3)
173                         rprintf(FINFO, "process has gid %ld\n", (long)mygid);
174         }
175         return gid == mygid;
176 #endif
177 }
178
179 static gid_t match_gid(gid_t gid)
180 {
181         static gid_t last_in = GID_NONE, last_out = GID_NONE;
182         struct idlist *list = gidlist;
183
184         if (gid == last_in)
185                 return last_out;
186
187         last_in = gid;
188
189         while (list) {
190                 if (list->id == (int)gid) {
191                         last_out = (gid_t)list->id2;
192                         return last_out;
193                 }
194                 list = list->next;
195         }
196         
197         if (am_root)
198                 last_out = gid;
199         else
200                 last_out = GID_NONE;
201         return last_out;
202 }
203
204 /* add a uid to the list of uids */
205 void add_uid(uid_t uid)
206 {
207         struct idlist *list = uidlist;
208         char *name;
209
210         if (numeric_ids) return;
211
212         /* don't map root */
213         if (uid==0) return;
214
215         if (!list) {
216                 if (!(name = uid_to_name(uid))) return;
217                 uidlist = add_list((int)uid, name);
218                 return;
219         }
220
221         while (list->next) {
222                 if (list->id == (int)uid) return;
223                 list = list->next;
224         }
225
226         if (list->id == (int)uid) return;
227
228         if (!(name = uid_to_name(uid))) return;
229
230         list->next = add_list((int)uid, name);
231 }
232
233 /* add a gid to the list of gids */
234 void add_gid(gid_t gid)
235 {
236         struct idlist *list = gidlist;
237         char *name;
238
239         if (numeric_ids) return;
240
241         /* don't map root */
242         if (gid==0) return;
243
244         if (!list) {
245                 if (!(name = gid_to_name(gid))) return;
246                 gidlist = add_list((int)gid, name);
247                 return;
248         }
249
250         while (list->next) {
251                 if (list->id == (int)gid) return;
252                 list = list->next;
253         }
254
255         if (list->id == (int)gid) return;
256
257         if (!(name = gid_to_name(gid))) return;
258
259         list->next = add_list((int)gid, name);
260 }
261
262
263 /* send a complete uid/gid mapping to the peer */
264 void send_uid_list(int f)
265 {
266         struct idlist *list;
267
268         if (numeric_ids) return;
269
270         if (preserve_uid) {
271                 /* we send sequences of uid/byte-length/name */
272                 list = uidlist;
273                 while (list) {
274                         int len = strlen(list->name);
275                         write_int(f, list->id);
276                         write_byte(f, len);
277                         write_buf(f, list->name, len);
278                         list = list->next;
279                 }
280
281                 /* terminate the uid list with a 0 uid. We explicitly exclude
282                  * 0 from the list */
283                 write_int(f, 0);
284         }
285
286         if (preserve_gid) {
287                 list = gidlist;
288                 while (list) {
289                         int len = strlen(list->name);
290                         write_int(f, list->id);
291                         write_byte(f, len);
292                         write_buf(f, list->name, len);
293                         list = list->next;
294                 }
295                 write_int(f, 0);
296         }
297 }
298
299 /* recv a complete uid/gid mapping from the peer and map the uid/gid
300  * in the file list to local names */
301 void recv_uid_list(int f, struct file_list *flist)
302 {
303         int id, i;
304         char *name;
305         struct idlist *list;
306
307         if (numeric_ids) return;
308
309         if (preserve_uid) {
310                 /* read the uid list */
311                 list = uidlist;
312                 while ((id = read_int(f)) != 0) {
313                         int len = read_byte(f);
314                         name = new_array(char, len+1);
315                         if (!name) out_of_memory("recv_uid_list");
316                         read_sbuf(f, name, len);
317                         if (!list) {
318                                 uidlist = add_list(id, name);
319                                 list = uidlist;
320                         } else {
321                                 list->next = add_list(id, name);
322                                 list = list->next;
323                         }
324                         list->id2 = map_uid(id, name);
325                         free(name);
326                 }
327                 if (verbose > 3) {
328                         for (list = uidlist; list; list = list->next) {
329                                 rprintf(FINFO, "uid %ld (%s) maps to %ld\n",
330                                     (long)list->id, list->name,
331                                     (long)list->id2);
332                         }
333                 }
334         }
335
336
337         if (preserve_gid) {
338                 /* and the gid list */
339                 list = gidlist;
340                 while ((id = read_int(f)) != 0) {
341                         int len = read_byte(f);
342                         name = new_array(char, len+1);
343                         if (!name) out_of_memory("recv_uid_list");
344                         read_sbuf(f, name, len);
345                         if (!list) {
346                                 gidlist = add_list(id, name);
347                                 list = gidlist;
348                         } else {
349                                 list->next = add_list(id, name);
350                                 list = list->next;
351                         }
352                         list->id2 = map_gid(id, name);
353                         if (!am_root && !is_in_group(list->id2))
354                                 list->id2 = GID_NONE;
355                         free(name);
356                 }
357                 if (verbose > 3) {
358                         for (list = gidlist; list; list = list->next) {
359                                 rprintf(FINFO, "gid %ld (%s) maps to %ld\n",
360                                     (long)list->id, list->name,
361                                     (long)list->id2);
362                         }
363                 }
364         }
365
366         if (!(am_root && preserve_uid) && !preserve_gid) return;
367
368         /* now convert the uid/gid of all files in the list to the mapped
369          * uid/gid */
370         for (i = 0; i < flist->count; i++) {
371                 if (am_root && preserve_uid && flist->files[i]->uid != 0)
372                         flist->files[i]->uid = match_uid(flist->files[i]->uid);
373                 if (preserve_gid && (!am_root || flist->files[i]->gid != 0))
374                         flist->files[i]->gid = match_gid(flist->files[i]->gid);
375         }
376 }