6 use Digest
::MD5
qw(md5);
26 require Digest
::SHA
::PurePerl
;
27 Digest
::SHA
::PurePerl
->import(
30 die "One of Digest::SHA or Digest::SHA1 or Digest::SHA::PurePerl "
31 . "must be available\n";
37 $self->{uuid
} = '' unless $self->{uuid
};
38 my @md5 = unpack('C*', md5
(time . $$ . rand() . join(':',%$self)));
39 $md5[6] = 0x40 | ($md5[6] & 0x0F); # Version 4 -- random
40 $md5[8] = 0x80 | ($md5[8] & 0x3F); # RFC 4122 specification
42 '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x',
46 sub _remove_ssh_leftovers
{
48 system "rm -f '$Girocco::Config::chroot/etc/sshkeys/$self->{name}'";
49 system "rm -f '$Girocco::Config::chroot/etc/sshcerts/${Girocco::Config::nickname}_$self->{name}'_user_*.pem";
54 my (undef, undef, $gid) = getgrnam($Girocco::Config
::owning_group
||'');
55 my $owngroupid = $gid ?
$gid : 65534;
56 Girocco
::User
->load($self->{name
}) and die "User $self->{name} already exists";
57 $self->{uuid
} = $self->_gen_uuid;
58 my $email_uuid = join ',', $self->{email
}, $self->{uuid
};
59 filedb_atomic_append
(jailed_file
('/etc/passwd'),
60 join(':', $self->{name
}, 'x', '\i', $owngroupid, $email_uuid, '/', '/bin/git-shell-verify'));
61 $self->_remove_ssh_leftovers;
66 filedb_atomic_edit
(jailed_file
('/etc/passwd'),
70 if ($self->{name
} eq (split /:/)[0]) {
71 # preserve all but login name and comment field
72 my @fields=split(/:/, $_, -1);
73 $fields[0] = $self->{name
};
74 $self->{uuid
} = (split(',', $fields[4]))[1] || '';
75 $self->{uuid
} or $self->{uuid
} = $self->_gen_uuid;
76 $fields[4] = join(',', $self->{email
}, $self->{uuid
});
77 return join(':', @fields)."\n";
87 $self->_remove_ssh_leftovers;
88 filedb_atomic_edit
(jailed_file
('/etc/passwd'),
90 $self->{name
} ne (split /:/)[0] and return $_;
97 '/etc/sshkeys/'.$self->{name
};
102 open F
, "<".jailed_file
($self->_sshkey_path) or die "sshkey load failed: $!";
108 if (/^ssh-(?:dss|rsa) /) {
110 } elsif (/^# ([A-Z]+)AUTH ([0-9a-f]+) (\d+)/) {
112 $auth = $2 unless (time >= $expire);
113 $authtype = $1 if $auth;
117 my $keys = join("\n", @keys); chomp $keys;
118 ($keys, $auth, $authtype);
124 foreach (split /\r\n|\r|\n/, $keys) {
125 next if /^[ \t]*$/ || /^[ \t]*#/;
128 return join("\n", @lines);
133 $self->{keys} = _trimkeys
($self->{keys} || '');
134 open F
, ">".jailed_file
($self->_sshkey_path) or die "sshkey failed: $!";
135 if (defined($self->{auth
}) && $self->{auth
}) {
136 my $expire = time + 24 * 3600;
137 my $typestr = $self->{authtype
} ?
uc($self->{authtype
}) : 'REPO';
138 print F
"# ${typestr}AUTH $self->{auth} $expire\n";
140 print F
$self->{keys}."\n";
142 chmod 0664, jailed_file
($self->_sshkey_path);
145 # private constructor, do not use
149 Girocco
::User
::valid_name
($name) or die "refusing to create user with invalid name ($name)!";
150 my $proj = { name
=> $name };
155 # public constructor #0
156 # creates a virtual user not connected to disk record
157 # you can conjure() it later to disk
161 my $self = $class->_new($name);
165 # public constructor #1
170 open F
, jailed_file
("/etc/passwd") or die "user load failed: $!";
174 next unless (shift eq $name);
176 my $self = $class->_new($name);
179 (undef, $self->{uid
}, undef, $email_uuid) = @_;
180 ($self->{keys}, $self->{auth
}, $self->{authtype
}) = $self->_sshkey_load;
181 ($self->{email
}, $self->{uuid
}) = split ',', $email_uuid;
184 $self->{uuid
} or $self->_passwd_update;
191 # public constructor #2
196 open F
, jailed_file
("/etc/passwd") or die "user load failed: $!";
200 next unless ($_[2] eq $uid);
202 my $self = $class->_new($_[0]);
205 (undef, undef, $self->{uid
}, undef, $email_uuid) = @_;
206 ($self->{keys}, $self->{auth
}, $self->{authtype
}) = $self->_sshkey_load;
207 ($self->{email
}, $self->{uuid
}) = split ',', $email_uuid;
210 $self->{uuid
} or $self->_passwd_update;
217 # $user may not be in sane state if this returns false!
221 my $cgi = $gcgi->cgi;
223 $self->{name
} = $gcgi->wparam('name');
224 Girocco
::User
::valid_name
($self->{name
})
225 or $gcgi->err("Name contains invalid characters.");
227 $self->{email
} = $gcgi->wparam('email');
228 valid_email
($self->{email
})
229 or $gcgi->err("Your email sure looks weird...?");
231 $self->keys_fill($gcgi);
237 my $email = shift || '';
239 if (valid_email
($email)) {
240 $self->{email
} = $email;
241 $self->_passwd_update;
243 $gcgi->err("Your email sure looks weird...?");
246 not $gcgi->err_check;
251 my ($type, $bits, $fingerprint, $comment) = sshpub_validate
($key);
252 return $type ?
1 : 0;
258 my $cgi = $gcgi->cgi;
260 $self->{keys} = _trimkeys
($cgi->param('keys'));
261 length($self->{keys}) <= 4096
262 or $gcgi->err("The list of keys is more than 4kb. Do you really need that much?");
263 foreach (split /\r?\n/, $self->{keys}) {
265 /^ssh-(?:dss|rsa) [0-9A-Za-z+\/=]+ \S
+@\S
+$/ && _checkkey
($_)
266 or $keyval=CGI
::escapeHTML
($_),$gcgi->err(<<EOT);
267 Your ssh key ("$keyval") appears to have an invalid format
268 (does not start with ssh-dss or ssh-rsa or does not end with <tt>\@</tt>-identifier) -
269 maybe your browser has split a single key onto multiple lines?
273 not $gcgi->err_check;
284 my @keys = split(/\r?\n/, $self->{keys});
287 my %types = ('ssh-dss' => 'DSA', 'ssh-rsa' => 'RSA');
291 my ($type, $bits, $fingerprint, $comment) = sshpub_validate
($_);
292 next unless $type && $types{$type};
293 my $euser = CGI
::escapeHTML
(CGI
::Util
::escape
($self->{name
}));
294 $html .= "<li>$bits <tt>$fingerprint</tt> ($types{$type}) $comment";
295 $html .= "<br /><a target=\"_blank\" href=\"/usercert.cgi/$euser/$line/".
296 $Girocco::Config
::nickname
."_${euser}_user_$line.pem\">".
297 "download https push user authentication certificate</a> <sup>".
298 "<a target=\"_blank\" href=\"$Girocco::Config::htmlurl/httpspush.html\">".
299 "(learn more)</a></sup>"
300 if $type eq 'ssh-rsa' && $Girocco::Config
::httpspushurl
&&
301 $Girocco::Config
::clientcert
&&
302 $Girocco::Config
::clientkey
;
312 $type = 'REPO' unless $type =~ /^[A-Z]+$/;
314 $self->{authtype
} = $type;
315 $self->{auth
} = sha1_hex
(time . $$ . rand() . $self->{keys});
323 delete $self->{auth
};
324 delete $self->{authtype
};
330 return @
{$self->{projects
}} if defined($self->{projects
});
331 my @projects = filedb_atomic_grep
(jailed_file
('/etc/group'),
335 my ($group, $users) = (split /:/)[0,3];
336 $group if $users && $users =~ /(^|,)\Q$self->{name}\E(,|$)/;
339 $self->{projects
} = \
@projects;
340 @
{$self->{projects
}};
353 foreach ($self->get_projects) {
354 if (Girocco
::Project
::does_exist
($_)) {
355 my $project = Girocco
::Project
->load($_);
356 $project->update if $project->remove_user($self->{name
});
360 $self->_passwd_remove;
367 /^[a-zA-Z0-9+._-]+$/;
372 Girocco
::User
::valid_name
($name) or die "tried to query for user with invalid name $name!";
373 (-e jailed_file
("/etc/sshkeys/$name"));
378 $Girocco::Config
::chrooted
and undef; # TODO for ACLs within chroot
379 scalar(getpwnam($name));