Commit | Line | Data |
---|---|---|
ade7292a | 1 | /* |
0f78b815 WD |
2 | * Handle the mapping of uid/gid and user/group names between systems. |
3 | * | |
4 | * Copyright (C) 1996 Andrew Tridgell | |
5 | * Copyright (C) 1996 Paul Mackerras | |
b3bf9b9d | 6 | * Copyright (C) 2004-2009 Wayne Davison |
0f78b815 WD |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify | |
8e41b68e WD |
9 | * it under the terms of the GNU General Public License as published by |
10 | * the Free Software Foundation; either version 3 of the License, or | |
11 | * (at your option) any later version. | |
0f78b815 WD |
12 | * |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
e7c67065 | 18 | * You should have received a copy of the GNU General Public License along |
4fd842f9 | 19 | * with this program; if not, visit the http://fsf.org website. |
0f78b815 WD |
20 | */ |
21 | ||
22 | /* If the source username/group does not exist on the target then use | |
23 | * the numeric IDs. Never do any mapping for uid=0 or gid=0 as these | |
24 | * are special. */ | |
f6c34742 AT |
25 | |
26 | #include "rsync.h" | |
2df20057 WD |
27 | #include "ifuncs.h" |
28 | #include "itypes.h" | |
ab14d01a | 29 | #include "io.h" |
f6c34742 | 30 | |
283887d7 | 31 | extern int am_root; |
f6c34742 AT |
32 | extern int preserve_uid; |
33 | extern int preserve_gid; | |
1c3344a1 | 34 | extern int preserve_acls; |
f6c34742 | 35 | extern int numeric_ids; |
3b83a220 | 36 | extern gid_t our_gid; |
2df20057 WD |
37 | extern char *usermap; |
38 | extern char *groupmap; | |
f6c34742 | 39 | |
142a5e7b WD |
40 | #ifdef HAVE_GETGROUPS |
41 | # ifndef GETGROUPS_T | |
42 | # define GETGROUPS_T gid_t | |
43 | # endif | |
44 | #endif | |
45 | ||
46 | #define GID_NONE ((gid_t)-1) | |
47 | ||
2df20057 WD |
48 | #define NFLAGS_WILD_NAME_MATCH (1<<0) |
49 | #define NFLAGS_NAME_MATCH (1<<1) | |
50 | ||
f6c34742 AT |
51 | struct idlist { |
52 | struct idlist *next; | |
c78cb8f3 | 53 | const char *name; |
d6b422a6 WD |
54 | id_t id, id2; |
55 | uint16 flags; | |
f6c34742 AT |
56 | }; |
57 | ||
2df20057 WD |
58 | static struct idlist *uidlist, *uidmap; |
59 | static struct idlist *gidlist, *gidmap; | |
f6c34742 | 60 | |
c78cb8f3 | 61 | static struct idlist *add_to_list(struct idlist **root, id_t id, const char *name, |
d6b422a6 | 62 | id_t id2, uint16 flags) |
f6c34742 | 63 | { |
d49def48 WD |
64 | struct idlist *node = new(struct idlist); |
65 | if (!node) | |
66 | out_of_memory("add_to_list"); | |
67 | node->next = *root; | |
68 | node->name = name; | |
69 | node->id = id; | |
70 | node->id2 = id2; | |
d6b422a6 | 71 | node->flags = flags; |
d49def48 WD |
72 | *root = node; |
73 | return node; | |
f6c34742 AT |
74 | } |
75 | ||
f6c34742 | 76 | /* turn a uid into a user name */ |
d7205694 | 77 | char *uid_to_user(uid_t uid) |
f6c34742 AT |
78 | { |
79 | struct passwd *pass = getpwuid(uid); | |
d49def48 WD |
80 | if (pass) |
81 | return strdup(pass->pw_name); | |
f6c34742 AT |
82 | return NULL; |
83 | } | |
84 | ||
85 | /* turn a gid into a group name */ | |
d7205694 | 86 | char *gid_to_group(gid_t gid) |
f6c34742 AT |
87 | { |
88 | struct group *grp = getgrgid(gid); | |
d49def48 WD |
89 | if (grp) |
90 | return strdup(grp->gr_name); | |
f6c34742 AT |
91 | return NULL; |
92 | } | |
93 | ||
d7205694 WD |
94 | /* Parse a user name or (optionally) a number into a uid */ |
95 | int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok) | |
96 | { | |
97 | struct passwd *pass; | |
98 | if (!name || !*name) | |
99 | return 0; | |
100 | if (num_ok && name[strspn(name, "0123456789")] == '\0') { | |
101 | *uid_p = atol(name); | |
102 | return 1; | |
103 | } | |
104 | if (!(pass = getpwnam(name))) | |
105 | return 0; | |
106 | *uid_p = pass->pw_uid; | |
107 | return 1; | |
108 | } | |
109 | ||
110 | /* Parse a group name or (optionally) a number into a gid */ | |
111 | int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok) | |
112 | { | |
113 | struct group *grp; | |
114 | if (!name || !*name) | |
115 | return 0; | |
116 | if (num_ok && name[strspn(name, "0123456789")] == '\0') { | |
117 | *gid_p = atol(name); | |
118 | return 1; | |
119 | } | |
120 | if (!(grp = getgrnam(name))) | |
121 | return 0; | |
122 | *gid_p = grp->gr_gid; | |
123 | return 1; | |
124 | } | |
125 | ||
5b540e86 WD |
126 | static int is_in_group(gid_t gid) |
127 | { | |
4f5b0756 | 128 | #ifdef HAVE_GETGROUPS |
a2687b64 | 129 | static gid_t last_in = GID_NONE, last_out; |
5b540e86 WD |
130 | static int ngroups = -2; |
131 | static GETGROUPS_T *gidset; | |
132 | int n; | |
133 | ||
134 | if (gid == last_in) | |
135 | return last_out; | |
136 | if (ngroups < -1) { | |
f567e9b3 | 137 | if ((ngroups = getgroups(0, NULL)) < 0) |
dbd8811b | 138 | ngroups = 0; |
72fc7ec5 | 139 | gidset = new_array(GETGROUPS_T, ngroups+1); |
f567e9b3 WD |
140 | if (!gidset) |
141 | out_of_memory("is_in_group"); | |
72fc7ec5 | 142 | if (ngroups > 0) |
5b540e86 | 143 | ngroups = getgroups(ngroups, gidset); |
72fc7ec5 WD |
144 | /* The default gid might not be in the list on some systems. */ |
145 | for (n = 0; n < ngroups; n++) { | |
3b83a220 | 146 | if (gidset[n] == our_gid) |
72fc7ec5 | 147 | break; |
5b540e86 | 148 | } |
72fc7ec5 | 149 | if (n == ngroups) |
3b83a220 | 150 | gidset[ngroups++] = our_gid; |
951e826b | 151 | if (DEBUG_GTE(OWN, 2)) { |
187e9c24 | 152 | int pos; |
f567e9b3 WD |
153 | char *gidbuf = new_array(char, ngroups*21+32); |
154 | if (!gidbuf) | |
155 | out_of_memory("is_in_group"); | |
10944395 WD |
156 | pos = snprintf(gidbuf, 32, "process has %d gid%s: ", |
157 | ngroups, ngroups == 1? "" : "s"); | |
84fa865c | 158 | for (n = 0; n < ngroups; n++) { |
10944395 | 159 | pos += snprintf(gidbuf+pos, 21, " %d", (int)gidset[n]); |
84fa865c | 160 | } |
187e9c24 | 161 | rprintf(FINFO, "%s\n", gidbuf); |
f567e9b3 | 162 | free(gidbuf); |
84fa865c | 163 | } |
5b540e86 WD |
164 | } |
165 | ||
166 | last_in = gid; | |
5b540e86 | 167 | for (n = 0; n < ngroups; n++) { |
a2687b64 WD |
168 | if (gidset[n] == gid) |
169 | return last_out = 1; | |
5b540e86 | 170 | } |
a2687b64 | 171 | return last_out = 0; |
5b540e86 WD |
172 | |
173 | #else | |
3b83a220 | 174 | return gid == our_gid; |
5b540e86 WD |
175 | #endif |
176 | } | |
177 | ||
2df20057 WD |
178 | /* Add a uid/gid to its list of ids. Only called on receiving side. */ |
179 | static struct idlist *recv_add_id(struct idlist **idlist_ptr, struct idlist *idmap, | |
180 | id_t id, const char *name) | |
d49def48 | 181 | { |
d49def48 | 182 | struct idlist *node; |
2df20057 WD |
183 | int flag; |
184 | id_t id2; | |
d49def48 | 185 | |
2df20057 WD |
186 | if (!name) |
187 | name = ""; | |
d49def48 | 188 | |
2df20057 WD |
189 | for (node = idmap; node; node = node->next) { |
190 | if (node->flags & NFLAGS_WILD_NAME_MATCH) { | |
191 | if (!wildmatch(node->name, name)) | |
192 | continue; | |
193 | } else if (node->flags & NFLAGS_NAME_MATCH) { | |
194 | if (strcmp(node->name, name) != 0) | |
195 | continue; | |
196 | } else if (node->name) { | |
c2dd3ec3 | 197 | if (id < node->id || (unsigned long)id > (unsigned long)node->name) |
2df20057 WD |
198 | continue; |
199 | } else { | |
200 | if (node->id != id) | |
201 | continue; | |
202 | } | |
203 | break; | |
d49def48 | 204 | } |
2df20057 WD |
205 | if (node) |
206 | id2 = node->id2; | |
207 | else if (*name && id) { | |
46d68be3 | 208 | if (idlist_ptr == &uidlist) { |
2df20057 | 209 | uid_t uid; |
56017d31 | 210 | id2 = user_to_uid(name, &uid, False) ? uid : id; |
2df20057 WD |
211 | } else { |
212 | gid_t gid; | |
56017d31 | 213 | id2 = group_to_gid(name, &gid, False) ? gid : id; |
2df20057 WD |
214 | } |
215 | } else | |
216 | id2 = id; | |
d49def48 | 217 | |
46d68be3 | 218 | flag = idlist_ptr == &gidlist && !am_root && !is_in_group(id2) ? FLAG_SKIP_GROUP : 0; |
2df20057 | 219 | node = add_to_list(idlist_ptr, id, *name ? name : NULL, id2, flag); |
d49def48 | 220 | |
951e826b | 221 | if (DEBUG_GTE(OWN, 2)) { |
2df20057 | 222 | rprintf(FINFO, "%sid %u(%s) maps to %u\n", |
46d68be3 | 223 | idlist_ptr == &uidlist ? "u" : "g", |
2df20057 | 224 | (unsigned)id, name, (unsigned)id2); |
d49def48 WD |
225 | } |
226 | ||
d6b422a6 | 227 | return node; |
d49def48 WD |
228 | } |
229 | ||
ade7292a | 230 | /* this function is a definate candidate for a faster algorithm */ |
496c809f | 231 | uid_t match_uid(uid_t uid) |
ade7292a | 232 | { |
2df20057 | 233 | static uid_t last_in = -1, last_out = -1; |
d49def48 WD |
234 | struct idlist *list; |
235 | ||
ade7292a WD |
236 | if (uid == last_in) |
237 | return last_out; | |
238 | ||
239 | last_in = uid; | |
240 | ||
d49def48 | 241 | for (list = uidlist; list; list = list->next) { |
d6b422a6 | 242 | if (list->id == uid) |
2df20057 | 243 | break; |
ade7292a WD |
244 | } |
245 | ||
2df20057 WD |
246 | if (!list) |
247 | list = recv_add_id(&uidlist, uidmap, uid, NULL); | |
248 | ||
249 | return last_out = list->id2; | |
ade7292a WD |
250 | } |
251 | ||
d6b422a6 | 252 | gid_t match_gid(gid_t gid, uint16 *flags_ptr) |
f6c34742 | 253 | { |
4504b225 | 254 | static struct idlist *last = NULL; |
d49def48 WD |
255 | struct idlist *list; |
256 | ||
4504b225 WD |
257 | if (last && gid == last->id) |
258 | list = last; | |
259 | else { | |
260 | for (list = gidlist; list; list = list->next) { | |
261 | if (list->id == gid) | |
262 | break; | |
263 | } | |
264 | if (!list) | |
2df20057 | 265 | list = recv_add_id(&gidlist, gidmap, gid, NULL); |
4504b225 | 266 | last = list; |
f6c34742 | 267 | } |
d49def48 | 268 | |
d6b422a6 WD |
269 | if (flags_ptr && list->flags & FLAG_SKIP_GROUP) |
270 | *flags_ptr |= FLAG_SKIP_GROUP; | |
4504b225 | 271 | return list->id2; |
f6c34742 AT |
272 | } |
273 | ||
d49def48 | 274 | /* Add a uid to the list of uids. Only called on sending side. */ |
c78cb8f3 | 275 | const char *add_uid(uid_t uid) |
f6c34742 | 276 | { |
d49def48 | 277 | struct idlist *list; |
496c809f | 278 | struct idlist *node; |
f6c34742 | 279 | |
d49def48 | 280 | if (uid == 0) /* don't map root */ |
496c809f | 281 | return NULL; |
f6c34742 | 282 | |
d49def48 | 283 | for (list = uidlist; list; list = list->next) { |
d6b422a6 | 284 | if (list->id == uid) |
496c809f | 285 | return NULL; |
f6c34742 AT |
286 | } |
287 | ||
d7205694 | 288 | node = add_to_list(&uidlist, uid, uid_to_user(uid), 0, 0); |
496c809f | 289 | return node->name; |
f6c34742 AT |
290 | } |
291 | ||
d49def48 | 292 | /* Add a gid to the list of gids. Only called on sending side. */ |
c78cb8f3 | 293 | const char *add_gid(gid_t gid) |
f6c34742 | 294 | { |
d49def48 | 295 | struct idlist *list; |
496c809f | 296 | struct idlist *node; |
f6c34742 | 297 | |
d49def48 | 298 | if (gid == 0) /* don't map root */ |
496c809f | 299 | return NULL; |
f6c34742 | 300 | |
d49def48 | 301 | for (list = gidlist; list; list = list->next) { |
d6b422a6 | 302 | if (list->id == gid) |
496c809f | 303 | return NULL; |
f6c34742 AT |
304 | } |
305 | ||
d7205694 | 306 | node = add_to_list(&gidlist, gid, gid_to_group(gid), 0, 0); |
496c809f | 307 | return node->name; |
f6c34742 AT |
308 | } |
309 | ||
f6c34742 | 310 | /* send a complete uid/gid mapping to the peer */ |
d6b422a6 | 311 | void send_id_list(int f) |
f6c34742 AT |
312 | { |
313 | struct idlist *list; | |
314 | ||
1c3344a1 | 315 | if (preserve_uid || preserve_acls) { |
d49def48 | 316 | int len; |
f6c34742 | 317 | /* we send sequences of uid/byte-length/name */ |
d49def48 WD |
318 | for (list = uidlist; list; list = list->next) { |
319 | if (!list->name) | |
320 | continue; | |
321 | len = strlen(list->name); | |
f31514ad | 322 | write_varint30(f, list->id); |
f6c34742 AT |
323 | write_byte(f, len); |
324 | write_buf(f, list->name, len); | |
f6c34742 AT |
325 | } |
326 | ||
327 | /* terminate the uid list with a 0 uid. We explicitly exclude | |
84fa865c | 328 | * 0 from the list */ |
f31514ad | 329 | write_varint30(f, 0); |
f6c34742 AT |
330 | } |
331 | ||
1c3344a1 | 332 | if (preserve_gid || preserve_acls) { |
d49def48 WD |
333 | int len; |
334 | for (list = gidlist; list; list = list->next) { | |
335 | if (!list->name) | |
336 | continue; | |
337 | len = strlen(list->name); | |
f31514ad | 338 | write_varint30(f, list->id); |
f6c34742 AT |
339 | write_byte(f, len); |
340 | write_buf(f, list->name, len); | |
f6c34742 | 341 | } |
f31514ad | 342 | write_varint30(f, 0); |
f6c34742 AT |
343 | } |
344 | } | |
345 | ||
496c809f | 346 | uid_t recv_user_name(int f, uid_t uid) |
283887d7 | 347 | { |
d6b422a6 | 348 | struct idlist *node; |
283887d7 WD |
349 | int len = read_byte(f); |
350 | char *name = new_array(char, len+1); | |
351 | if (!name) | |
352 | out_of_memory("recv_user_name"); | |
353 | read_sbuf(f, name, len); | |
0b52f94d WD |
354 | if (numeric_ids < 0) { |
355 | free(name); | |
356 | name = NULL; | |
357 | } | |
2df20057 | 358 | node = recv_add_id(&uidlist, uidmap, uid, name); /* node keeps name's memory */ |
d6b422a6 | 359 | return node->id2; |
283887d7 WD |
360 | } |
361 | ||
d6b422a6 | 362 | gid_t recv_group_name(int f, gid_t gid, uint16 *flags_ptr) |
283887d7 | 363 | { |
d6b422a6 | 364 | struct idlist *node; |
283887d7 WD |
365 | int len = read_byte(f); |
366 | char *name = new_array(char, len+1); | |
367 | if (!name) | |
368 | out_of_memory("recv_group_name"); | |
369 | read_sbuf(f, name, len); | |
0b52f94d WD |
370 | if (numeric_ids < 0) { |
371 | free(name); | |
372 | name = NULL; | |
373 | } | |
2df20057 | 374 | node = recv_add_id(&gidlist, gidmap, gid, name); /* node keeps name's memory */ |
d6b422a6 WD |
375 | if (flags_ptr && node->flags & FLAG_SKIP_GROUP) |
376 | *flags_ptr |= FLAG_SKIP_GROUP; | |
377 | return node->id2; | |
283887d7 WD |
378 | } |
379 | ||
f6c34742 | 380 | /* recv a complete uid/gid mapping from the peer and map the uid/gid |
84fa865c | 381 | * in the file list to local names */ |
d6b422a6 | 382 | void recv_id_list(int f, struct file_list *flist) |
f6c34742 | 383 | { |
d6b422a6 WD |
384 | id_t id; |
385 | int i; | |
f6c34742 | 386 | |
0b52f94d | 387 | if ((preserve_uid || preserve_acls) && numeric_ids <= 0) { |
f6c34742 | 388 | /* read the uid list */ |
f31514ad | 389 | while ((id = read_varint30(f)) != 0) |
d6b422a6 | 390 | recv_user_name(f, id); |
f6c34742 AT |
391 | } |
392 | ||
0b52f94d | 393 | if ((preserve_gid || preserve_acls) && numeric_ids <= 0) { |
d49def48 | 394 | /* read the gid list */ |
f31514ad | 395 | while ((id = read_varint30(f)) != 0) |
d6b422a6 | 396 | recv_group_name(f, id, NULL); |
f6c34742 AT |
397 | } |
398 | ||
a217c453 | 399 | /* Now convert all the uids/gids from sender values to our values. */ |
1c3344a1 | 400 | #ifdef SUPPORT_ACLS |
2df20057 | 401 | if (preserve_acls && (!numeric_ids || usermap || groupmap)) |
a217c453 | 402 | match_acl_ids(); |
1c3344a1 | 403 | #endif |
2df20057 | 404 | if (am_root && preserve_uid && (!numeric_ids || usermap)) { |
9decb4d2 | 405 | for (i = 0; i < flist->used; i++) |
d6b422a6 | 406 | F_OWNER(flist->files[i]) = match_uid(F_OWNER(flist->files[i])); |
d49def48 | 407 | } |
2df20057 | 408 | if (preserve_gid && (!am_root || !numeric_ids || groupmap)) { |
9decb4d2 | 409 | for (i = 0; i < flist->used; i++) { |
d6b422a6 WD |
410 | F_GROUP(flist->files[i]) = match_gid(F_GROUP(flist->files[i]), |
411 | &flist->files[i]->flags); | |
412 | } | |
5e58e3f9 | 413 | } |
f6c34742 | 414 | } |
2df20057 WD |
415 | |
416 | void parse_name_map(char *map, BOOL usernames) | |
417 | { | |
418 | struct idlist **idmap_ptr = usernames ? &uidmap : &gidmap; | |
419 | struct idlist **idlist_ptr = usernames ? &uidlist : &gidlist; | |
420 | char *colon, *end, *name, *cp = map + strlen(map); | |
421 | id_t id1; | |
422 | uint16 flags; | |
423 | ||
424 | /* Parse the list in reverse, so the order in the struct is right. */ | |
425 | while (1) { | |
426 | end = cp; | |
427 | while (cp > map && cp[-1] != ',') cp--; | |
428 | if (!(colon = strchr(cp, ':'))) { | |
429 | rprintf(FERROR, "No colon found in --%smap: %s\n", | |
430 | usernames ? "user" : "group", cp); | |
431 | exit_cleanup(RERR_SYNTAX); | |
432 | } | |
433 | if (!colon[1]) { | |
434 | rprintf(FERROR, "No name found after colon --%smap: %s\n", | |
435 | usernames ? "user" : "group", cp); | |
436 | exit_cleanup(RERR_SYNTAX); | |
437 | } | |
438 | *colon = '\0'; | |
439 | ||
440 | if (isDigit(cp)) { | |
441 | char *dash = strchr(cp, '-'); | |
442 | if (strspn(cp, "0123456789-") != (size_t)(colon - cp) | |
443 | || (dash && (!dash[1] || strchr(dash+1, '-')))) { | |
2df20057 WD |
444 | rprintf(FERROR, "Invalid number in --%smap: %s\n", |
445 | usernames ? "user" : "group", cp); | |
446 | exit_cleanup(RERR_SYNTAX); | |
447 | } | |
448 | if (dash) | |
449 | name = (char *)atol(dash+1); | |
450 | else | |
451 | name = (char *)0; | |
452 | flags = 0; | |
453 | id1 = atol(cp); | |
454 | } else if (strpbrk(cp, "*[?")) { | |
455 | flags = NFLAGS_WILD_NAME_MATCH; | |
456 | name = cp; | |
457 | id1 = 0; | |
458 | } else { | |
459 | flags = NFLAGS_NAME_MATCH; | |
460 | name = cp; | |
461 | id1 = 0; | |
462 | } | |
463 | ||
56017d31 | 464 | if (usernames) { |
2df20057 | 465 | uid_t uid; |
56017d31 | 466 | if (user_to_uid(colon+1, &uid, True)) |
2df20057 WD |
467 | add_to_list(idmap_ptr, id1, name, uid, flags); |
468 | else { | |
469 | rprintf(FERROR, | |
470 | "Unknown --usermap name on receiver: %s\n", | |
471 | colon+1); | |
472 | } | |
473 | } else { | |
474 | gid_t gid; | |
56017d31 | 475 | if (group_to_gid(colon+1, &gid, True)) |
2df20057 WD |
476 | add_to_list(idmap_ptr, id1, name, gid, flags); |
477 | else { | |
478 | rprintf(FERROR, | |
479 | "Unknown --groupmap name on receiver: %s\n", | |
480 | colon+1); | |
481 | } | |
482 | } | |
483 | ||
484 | if (cp == map) | |
485 | break; | |
486 | ||
487 | *--cp = '\0'; /* replace comma */ | |
488 | } | |
489 | ||
490 | /* The 0 user/group doesn't get its name sent, so add it explicitly. */ | |
491 | recv_add_id(idlist_ptr, *idmap_ptr, 0, | |
d7205694 | 492 | numeric_ids ? NULL : usernames ? uid_to_user(0) : gid_to_group(0)); |
2df20057 | 493 | } |
d7205694 WD |
494 | |
495 | #ifdef HAVE_GETGROUPLIST | |
496 | const char *getallgroups(uid_t uid, gid_t *gid_list, int *size_ptr) | |
497 | { | |
498 | struct passwd *pw; | |
499 | if ((pw = getpwuid(uid)) == NULL) | |
500 | return "getpwuid failed"; | |
501 | /* Get all the process's groups, with the pw_gid group first. */ | |
502 | if (getgrouplist(pw->pw_name, pw->pw_gid, gid_list, size_ptr) < 0) | |
503 | return "getgrouplist failed"; | |
504 | /* Paranoia: is the default group not first in the list? */ | |
505 | if (gid_list[0] != pw->pw_gid) { | |
506 | int j; | |
507 | for (j = 0; j < *size_ptr; j++) { | |
508 | if (gid_list[j] == pw->pw_gid) { | |
509 | gid_list[j] = gid_list[0]; | |
510 | gid_list[0] = pw->pw_gid; | |
511 | break; | |
512 | } | |
513 | } | |
514 | } | |
515 | return NULL; | |
516 | } | |
517 | #endif |