view make-username.pl @ 7:462ba4c70c65

Eliminated global vars (undeclared variables suck). Added check for no username available. Cleanup up test code.
author Eris Caffee <discordia@eldalin.com>
date Mon, 18 Jul 2011 13:04:44 -0500
parents 065d2beb69a5
children 542b1fb6759d
line source
1 #!/usr/bin/env perl
3 ################################################################################
4 #
5 # Copyright (C) 2011 Sarah Eris Horsley Caffee
6 #
7 # This is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #
20 ################################################################################
21 #
22 # Returns a new unused username for use on a *nix server.
23 #
24 ################################################################################
26 use warnings;
27 use strict;
28 use English;
29 use File::Basename;
31 # main is a block. NO GLOBALS!
32 {
33 my $base ="";
34 my $user;
35 my $debug = 0;
36 my $maxlen = 8;
37 my $test = 0;
39 foreach my $arg (@ARGV) {
40 if ("-d" eq $arg) {
41 $debug = 1;
42 } elsif ($arg =~ /^-m/) {
43 $arg =~ s/^..//;
44 if ($arg =~ /^(\d+)$/) {
45 $maxlen = $1;
46 $debug and print "Setting maxlen to $maxlen\n";
47 } else {
48 printf(STDERR "Invalid username length specified: $arg\n");
49 }
50 } elsif ($arg eq "-h") {
51 usage();
52 exit 0;
53 } elsif ($arg eq "-t") {
54 $test = 1;
55 } else {
56 $base = $arg;
57 }
58 }
60 if (! $test) {
61 $user = make_username($base, $maxlen);
62 if (defined $user) {
63 printf("%s\n", $user);
64 } else {
65 printf(STDERR "No username available\n");
66 }
67 exit 0;
68 } else {
69 my $pwdfile = "passwd";
70 my ($base, $t1, $t2);
71 my @bases = ("", # should use default "user" as base. user and user1 taken
72 "eightchr",
73 "short",
74 "this *&% is not allowed &^$%&^m",
75 "123digits",
76 "this-is-a-domain.com",
77 '&$%#&$*&$%*&',
78 "testqwer", # testqwer taken
79 "testabc", # all testabc? taken
80 "testzx", # all testzx?? taken
81 "test", # all test??? taken
82 "t" # all z????? taken though 10435
83 );
85 foreach $base (@bases) {
86 $t1 = time;
87 $user = make_username($base, $maxlen, $pwdfile);
88 $t2 = time;
89 if (defined $user) {
90 printf("%30s %-".$maxlen."s in %ds\n", $base, $user, $t2 - $t1);
91 } else {
92 printf("%30s No username available\n", $base, $t2 - $t1);
93 }
94 }
96 $maxlen = 2;
97 $base = "z"; # all z? taken. Should return no username.
98 $t1 = time;
99 $user = make_username($base, $maxlen, $pwdfile);
100 $t2 = time;
101 if (defined $user) {
102 printf("%30s %-".$maxlen."s in %ds\n", $base, $user, $t2 - $t1);
103 } else {
104 printf("%30s No username available\n", $base, $t2 - $t1);
105 }
107 }
109 }
110 exit 0;
112 ################################################################################
113 sub make_username {
114 # Arguments:
115 # $base The string to use as the basis of the new username.
116 # Can be any string (such as an existing username or a domain name)
117 # But at most $maxlen characters will be used. Invalid characters will
118 # be removed.
119 # If the first character would be a number, an 'a' is prepended to the name.
120 # If blank, the string "user" will be used.
121 # $maxlen The maximum length of the username to create. If not specified,
122 # it defaults to 8. It must be 2 or more. (If it was 1 then there
123 # would be no way to prepend a number to it without having a digit as
124 # the first character of the new name.)
125 # $pwdfile The name of the passwd file to use for checking for new users.
126 # /etc/passwd by default. This is really a debugging option.
127 #
128 # Returns:
129 # A string containing the new username or undef if no username
130 # could be generated (unlikely)
132 my ($base, $maxlen, $pwdfile) = @_;
134 if ($base eq "") {
135 $base = "user";
136 }
138 ! defined $pwdfile and $pwdfile = "/etc/passwd";
140 if (! defined $maxlen) {
141 $maxlen = 8;
142 } else {
143 if ($maxlen !~ /^\d+$/) {
144 printf(STDERR "Error: non-numeric username length specified.\n");
145 return undef;
146 }
147 }
149 if (! -e $pwdfile) {
150 printf(STDERR "Error: non-existant passwd file specified: $pwdfile\n");
151 return undef;
152 }
154 # Remove non-alpha-numeric characters and make sure the first character
155 # is a letter.
156 $base =~ s/[^A-Za-z0-9]//g;
157 if ($base !~ /^[a-z]/ ) {
158 $base = "a".$base;
159 }
161 # Use the specified parameter as the base of the new username.
162 # Print it into a string of exactly $maxlen characters.
163 # Print the number into the last portion of the new username.
164 # Delete spaces (in case the passed username candidate was short).
165 # Churn until we find an unused name.
166 my $i=1;
167 my $numstr;
168 my $newuser = sprintf("%.".$maxlen."s", $base);
169 # print "maxlen $maxlen\n";
170 #print "newuser +$newuser+\n";
171 while ( (user_exists($newuser, $pwdfile)) && ($i < 10**($maxlen-1)) ) {
172 $numstr = sprintf("%d", $i);
173 $newuser = sprintf("%-".$maxlen."s", $base);
174 # print "1 newuser $newuser\n";
175 substr($newuser, -length($numstr), length($numstr), $numstr);
176 # print "2 newuser $newuser\n";
177 $newuser =~ s/ //g;
178 # print "3 newuser $newuser\n";
179 $i++;
180 }
182 $i >= 10**($maxlen-1) and return undef;
183 return $newuser;
184 }
186 ################################################################################
187 sub user_exists {
188 my ($user, $pwdfile) = @_;
190 my $retval = 0;
191 my $i;
192 my $buffer;
194 open my $handle, "<", $pwdfile;
195 while (sysread($handle, $buffer, 65536, 0)) {
196 if ($buffer =~ /(^|\n)${user}:/) {
197 $retval = 1;
198 last;
199 }
200 }
201 close $handle;
202 return $retval;
203 }
205 ################################################################################
206 sub usage {
207 printf(
208 "Usage: ".basename($PROGRAM_NAME)." [-h] [-d] [-mM] [-t] [basename]
210 Generate a new unused username suitable for assigning to a new account.
212 basename If specified this will be used as the basis of the new username
214 The basename will be stripped of all characters other than letters
215 and digits, and it will be truncated to no more than the maximum
216 allowed username length (8 by default, but settable with the
217 -m option).
218 In the event that a user already exists by the given name a number
219 will be inserted at the end of the username and sequentially
220 increased until an available name is found.
221 If basename is not specified, then \"user\" will be used as the base.
222 -h Print these instructions.
223 -d Enable debug messages.
224 -mM Set maximum allowed username length to M (default is 8).
225 -t Perform test code. You must have a suitable passwd file in the
226 current directory in order to run this.
227 ");
228 }