1#!/usr/bin/perl
2#
3
4use strict;
5
6use Getopt::Long;
7use Cwd qw(abs_path);
8
9my $opt_help = 0;
10my $opt_passwd_path = undef;
11my $opt_group_path = undef;
12my $opt_action = undef;
13my $opt_type = undef;
14my $opt_name = undef;
15my $opt_member = undef;
16my $opt_gid = 65534;# nogroup gid
17
18my $passwdfn = undef;
19my $groupfn = undef;
20my $memberfn = undef;
21my $actionfn = undef;
22
23sub passwd_add($$$$$);
24sub passwd_delete($$$$$);
25sub group_add($$$$$);
26sub group_delete($$$$$);
27sub member_add($$$$$);
28sub member_delete($$$$$);
29
30sub check_path($$);
31
32my $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
43sub 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
69usage(1) if (not $result);
70
71usage(0) if ($opt_help);
72
73if (not defined($opt_action)) {
74	usage(1, "missing: --action [add|delete]");
75}
76if ($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
88if (not defined($opt_type)) {
89	usage(1, "missing: --type [passwd|group|member]");
90}
91if ($opt_type eq "member" and not defined($opt_member)) {
92	usage(1, "missing: --member <member>");
93}
94my $opt_fullpath_passwd;
95my $opt_fullpath_group;
96if ($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
110if (not defined($opt_name)) {
111	usage(1, "missing: --name <name>");
112}
113if ($opt_name eq "") {
114	usage(1, "invalid: --name <name>");
115}
116
117exit $actionfn->($opt_fullpath_passwd, $opt_member, $opt_fullpath_group, $opt_name, $opt_gid);
118
119sub 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
136sub passwd_add_entry($$);
137
138sub 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
160sub group_add_entry($$);
161
162sub 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
184sub 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
193sub 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
202sub 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
211sub 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
220sub 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
232sub 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
244sub 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
256sub 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
268sub 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
282sub 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
296sub 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
321sub 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
352sub 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
372sub 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
395sub 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
417sub 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
435sub 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
459sub 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
477sub 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
500sub 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}
522