Revert "nwrap: Don't fail if we want to add an existing entry"
[obnox/cwrap/nss_wrapper.git] / nss_wrapper.pl
1 #!/usr/bin/perl
2 #
3
4 use strict;
5
6 use Getopt::Long;
7 use Cwd qw(abs_path);
8
9 my $opt_help = 0;
10 my $opt_passwd_path = undef;
11 my $opt_group_path = undef;
12 my $opt_action = undef;
13 my $opt_type = undef;
14 my $opt_name = undef;
15 my $opt_member = undef;
16 my $opt_gid = 65534;# nogroup gid
17
18 my $passwdfn = undef;
19 my $groupfn = undef;
20 my $memberfn = undef;
21 my $actionfn = undef;
22
23 sub passwd_add($$$$$);
24 sub passwd_delete($$$$$);
25 sub group_add($$$$$);
26 sub group_delete($$$$$);
27 sub member_add($$$$$);
28 sub member_delete($$$$$);
29
30 sub check_path($$);
31
32 my $result = GetOptions(
33         'help|h|?'      => \$opt_help,
34         'passwd_path=s' => \$opt_passwd_path,
35         'group_path=s'  => \$opt_group_path,
36         'action=s'      => \$opt_action,
37         'type=s'        => \$opt_type,
38         'name=s'        => \$opt_name,
39         'member=s'      => \$opt_member,
40         'gid=i'         => \$opt_gid
41 );
42
43 sub usage($;$)
44 {
45         my ($ret, $msg) = @_;
46
47         print $msg."\n\n" if defined($msg);
48
49         print "usage:
50
51         --help|-h|-?            Show this help.
52
53         --passwd_path <path>    Path of the 'passwd' file.
54         --group_path <path>     Path of the 'group' file.
55
56         --type <type>           'passwd', 'group' and 'member' are supported.
57
58         --action <action>       'add' or 'delete'.
59
60         --name <name>           The name of the object.
61
62         --member <member>       The name of the member.
63
64         --gid <gid>             Primary Group ID for new users.
65 ";
66         exit($ret);
67 }
68
69 usage(1) if (not $result);
70
71 usage(0) if ($opt_help);
72
73 if (not defined($opt_action)) {
74         usage(1, "missing: --action [add|delete]");
75 }
76 if ($opt_action eq "add") {
77         $passwdfn = \&passwd_add;
78         $groupfn = \&group_add;
79         $memberfn = \&member_add;
80 } elsif ($opt_action eq "delete") {
81         $passwdfn = \&passwd_delete;
82         $groupfn = \&group_delete;
83         $memberfn = \&member_delete;
84 } else {
85         usage(1, "invalid: --action [add|delete]: '$opt_action'");
86 }
87
88 if (not defined($opt_type)) {
89         usage(1, "missing: --type [passwd|group|member]");
90 }
91 if ($opt_type eq "member" and not defined($opt_member)) {
92         usage(1, "missing: --member <member>");
93 }
94 my $opt_fullpath_passwd;
95 my $opt_fullpath_group;
96 if ($opt_type eq "passwd") {
97         $actionfn = $passwdfn;
98         $opt_fullpath_passwd = check_path($opt_passwd_path, $opt_type);
99 } elsif ($opt_type eq "group") {
100         $actionfn = $groupfn;
101         $opt_fullpath_group = check_path($opt_group_path, $opt_type);
102 } elsif ($opt_type eq "member") {
103         $actionfn = $memberfn;
104         $opt_fullpath_passwd = check_path($opt_passwd_path, "passwd");
105         $opt_fullpath_group = check_path($opt_group_path, "group");
106 } else {
107         usage(1, "invalid: --type [passwd|group]: '$opt_type'")
108 }
109
110 if (not defined($opt_name)) {
111         usage(1, "missing: --name <name>");
112 }
113 if ($opt_name eq "") {
114         usage(1, "invalid: --name <name>");
115 }
116
117 exit $actionfn->($opt_fullpath_passwd, $opt_member, $opt_fullpath_group, $opt_name, $opt_gid);
118
119 sub check_path($$)
120 {
121         my ($path,$type) = @_;
122
123         if (not defined($path)) {
124                 usage(1, "missing: --$type\_path <path>");
125         }
126         if ($path eq "" or $path eq "/") {
127                 usage(1, "invalid: --$type\_path <path>: '$path'");
128         }
129         my $fullpath = abs_path($path);
130         if (not defined($fullpath)) {
131                 usage(1, "invalid: --$type\_path <path>: '$path'");
132         }
133         return $fullpath;
134 }
135
136 sub passwd_add_entry($$);
137
138 sub passwd_load($)
139 {
140         my ($path) = @_;
141         my @lines;
142         my $passwd = undef;
143
144         open(PWD, "<$path") or die("Unable to open '$path' for read");
145         @lines = <PWD>;
146         close(PWD);
147
148         $passwd->{array} = ();
149         $passwd->{name} = {};
150         $passwd->{uid} = {};
151         $passwd->{path} = $path;
152
153         foreach my $line (@lines) {
154                 passwd_add_entry($passwd, $line);
155         }
156
157         return $passwd;
158 }
159
160 sub group_add_entry($$);
161
162 sub group_load($)
163 {
164         my ($path) = @_;
165         my @lines;
166         my $group = undef;
167
168         open(GROUP, "<$path") or die("Unable to open '$path' for read");
169         @lines = <GROUP>;
170         close(GROUP);
171
172         $group->{array} = ();
173         $group->{name} = {};
174         $group->{gid} = {};
175         $group->{path} = $path;
176
177         foreach my $line (@lines) {
178                 group_add_entry($group, $line);
179         }
180
181         return $group;
182 }
183
184 sub passwd_lookup_name($$)
185 {
186         my ($passwd, $name) = @_;
187
188         return undef unless defined($passwd->{name}{$name});
189
190         return $passwd->{name}{$name};
191 }
192
193 sub group_lookup_name($$)
194 {
195         my ($group, $name) = @_;
196
197         return undef unless defined($group->{name}{$name});
198
199         return $group->{name}{$name};
200 }
201
202 sub passwd_lookup_uid($$)
203 {
204         my ($passwd, $uid) = @_;
205
206         return undef unless defined($passwd->{uid}{$uid});
207
208         return $passwd->{uid}{$uid};
209 }
210
211 sub group_lookup_gid($$)
212 {
213         my ($group, $gid) = @_;
214
215         return undef unless defined($group->{gid}{$gid});
216
217         return $group->{gid}{$gid};
218 }
219
220 sub passwd_get_free_uid($)
221 {
222         my ($passwd) = @_;
223         my $uid = 1000;
224
225         while (passwd_lookup_uid($passwd, $uid)) {
226                 $uid++;
227         }
228
229         return $uid;
230 }
231
232 sub group_get_free_gid($)
233 {
234         my ($group) = @_;
235         my $gid = 1000;
236
237         while (group_lookup_gid($group, $gid)) {
238                 $gid++;
239         }
240
241         return $gid;
242 }
243
244 sub passwd_add_entry($$)
245 {
246         my ($passwd, $str) = @_;
247
248         chomp $str;
249         my @e = split(':', $str);
250
251         push(@{$passwd->{array}}, \@e);
252         $passwd->{name}{$e[0]} = \@e;
253         $passwd->{uid}{$e[2]} = \@e;
254 }
255
256 sub group_add_entry($$)
257 {
258         my ($group, $str) = @_;
259
260         chomp $str;
261         my @e = split(':', $str);
262
263         push(@{$group->{array}}, \@e);
264         $group->{name}{$e[0]} = \@e;
265         $group->{gid}{$e[2]} = \@e;
266 }
267
268 sub passwd_remove_entry($$)
269 {
270         my ($passwd, $eref) = @_;
271
272         for (my $i = 0; defined($passwd->{array}[$i]); $i++) {
273                 if ($eref == $passwd->{array}[$i]) {
274                         $passwd->{array}[$i] = undef;
275                 }
276         }
277
278         delete $passwd->{name}{${$eref}[0]};
279         delete $passwd->{uid}{${$eref}[2]};
280 }
281
282 sub group_remove_entry($$)
283 {
284         my ($group, $eref) = @_;
285
286         for (my $i = 0; defined($group->{array}[$i]); $i++) {
287                 if ($eref == $group->{array}[$i]) {
288                         $group->{array}[$i] = undef;
289                 }
290         }
291
292         delete $group->{name}{${$eref}[0]};
293         delete $group->{gid}{${$eref}[2]};
294 }
295
296 sub group_add_member($$$)
297 {
298         my ($group, $eref, $username) = @_;
299
300         my @members;
301         my $str = @$eref[3] || undef;
302         if ($str) {
303                 @members = split(",", $str);
304         }
305
306         foreach my $member (@members) {
307                 if ($member and $member eq $username) {
308                         die("account[$username] is already member of '@$eref[0]'");
309                 }
310         }
311
312         push(@members, $username);
313
314         my $gwent = @$eref[0].":x:".@$eref[2].":".join(",", @members);
315
316         group_remove_entry($group, $eref);
317
318         group_add_entry($group, $gwent);
319 }
320
321 sub group_delete_member($$$)
322 {
323         my ($group, $eref, $username) = @_;
324
325         my @members = undef;
326         my $str = @$eref[3] || undef;
327         if ($str) {
328                 @members = split(",", $str);
329         }
330         my @new_members;
331         my $removed = 0;
332
333         foreach my $member (@members) {
334                 if ($member and $member ne $username) {
335                         push(@new_members, $member);
336                 } else {
337                         $removed = 1;
338                 }
339         }
340
341         if ($removed != 1) {
342                 die("account[$username] is not member of '@$eref[0]'");
343         }
344
345         my $gwent = @$eref[0].":x:".@$eref[2].":".join(",", @new_members);
346
347         group_remove_entry($group, $eref);
348
349         group_add_entry($group, $gwent);
350 }
351
352 sub passwd_save($)
353 {
354         my ($passwd) = @_;
355         my @lines = ();
356         my $path = $passwd->{path};
357         my $tmppath = $path.$$;
358
359         foreach my $eref (@{$passwd->{array}}) {
360                 next unless defined($eref);
361
362                 my $line = join(':', @{$eref});
363                 push(@lines, $line);
364         }
365
366         open(PWD, ">$tmppath") or die("Unable to open '$tmppath' for write");
367         print PWD join("\n", @lines)."\n";
368         close(PWD);
369         rename($tmppath, $path) or die("Unable to rename $tmppath => $path");
370 }
371
372 sub group_save($)
373 {
374         my ($group) = @_;
375         my @lines = ();
376         my $path = $group->{path};
377         my $tmppath = $path.$$;
378
379         foreach my $eref (@{$group->{array}}) {
380                 next unless defined($eref);
381
382                 my $line = join(':', @{$eref});
383                 if (scalar(@{$eref}) == 3) {
384                         $line .= ":";
385                 }
386                 push(@lines, $line);
387         }
388
389         open(GROUP, ">$tmppath") or die("Unable to open '$tmppath' for write");
390         print GROUP join("\n", @lines)."\n";
391         close(GROUP);
392         rename($tmppath, $path) or die("Unable to rename $tmppath => $path");
393 }
394
395 sub passwd_add($$$$$)
396 {
397         my ($path, $dummy, $dummy2, $name, $gid) = @_;
398
399         #print "passwd_add: '$name' in '$path'\n";
400
401         my $passwd = passwd_load($path);
402
403         my $e = passwd_lookup_name($passwd, $name);
404         die("account[$name] already exists in '$path'") if defined($e);
405
406         my $uid = passwd_get_free_uid($passwd);
407
408         my $pwent = $name.":x:".$uid.":".$gid.":".$name." gecos:/nodir:/bin/false";
409
410         passwd_add_entry($passwd, $pwent);
411
412         passwd_save($passwd);
413
414         return 0;
415 }
416
417 sub passwd_delete($$$$$)
418 {
419         my ($path, $dummy, $dummy2, $name, $dummy3) = @_;
420
421         #print "passwd_delete: '$name' in '$path'\n";
422
423         my $passwd = passwd_load($path);
424
425         my $e = passwd_lookup_name($passwd, $name);
426         die("account[$name] does not exists in '$path'") unless defined($e);
427
428         passwd_remove_entry($passwd, $e);
429
430         passwd_save($passwd);
431
432         return 0;
433 }
434
435 sub group_add($$$$$)
436 {
437         my ($dummy, $dummy2, $path, $name, $dummy3) = @_;
438
439         #print "group_add: '$name' in '$path'\n";
440
441         my $group = group_load($path);
442
443         my $e = group_lookup_name($group, $name);
444         die("group[$name] already exists in '$path'") if defined($e);
445
446         my $gid = group_get_free_gid($group);
447
448         my $gwent = $name.":x:".$gid.":"."";
449
450         group_add_entry($group, $gwent);
451
452         group_save($group);
453
454         #printf("%d\n", $gid);
455
456         return 0;
457 }
458
459 sub group_delete($$$$$)
460 {
461         my ($dummy, $dummy2, $path, $name, $dummy3) = @_;
462
463         #print "group_delete: '$name' in '$path'\n";
464
465         my $group = group_load($path);
466
467         my $e = group_lookup_name($group, $name);
468         die("group[$name] does not exists in '$path'") unless defined($e);
469
470         group_remove_entry($group, $e);
471
472         group_save($group);
473
474         return 0;
475 }
476
477 sub member_add($$$$$)
478 {
479         my ($passwd_path, $username, $group_path, $groupname, $dummy) = @_;
480
481         #print "member_add: adding '$username' in '$passwd_path' to '$groupname' in '$group_path'\n";
482
483         my $group = group_load($group_path);
484
485         my $g = group_lookup_name($group, $groupname);
486         die("group[$groupname] does not exists in '$group_path'") unless defined($g);
487
488         my $passwd = passwd_load($passwd_path);
489
490         my $u = passwd_lookup_name($passwd, $username);
491         die("account[$username] does not exists in '$passwd_path'") unless defined($u);
492
493         group_add_member($group, $g, $username);
494
495         group_save($group);
496
497         return 0;
498 }
499
500 sub member_delete($$$$$)
501 {
502         my ($passwd_path, $username, $group_path, $groupname, $dummy) = @_;
503
504         #print "member_delete: removing '$username' in '$passwd_path' from '$groupname' in '$group_path'\n";
505
506         my $group = group_load($group_path);
507
508         my $g = group_lookup_name($group, $groupname);
509         die("group[$groupname] does not exists in '$group_path'") unless defined($g);
510
511         my $passwd = passwd_load($passwd_path);
512
513         my $u = passwd_lookup_name($passwd, $username);
514         die("account[$username] does not exists in '$passwd_path'") unless defined($u);
515
516         group_delete_member($group, $g, $username);
517
518         group_save($group);
519
520         return 0;
521 }