Run the gitweb.cgi make for the all target
[girocco.git] / Girocco / User.pm
blobc076814e8545472b2399ea845d44d3e208bc2207
1 package Girocco::User;
3 use strict;
4 use warnings;
6 use Girocco::Config;
7 use Girocco::CGI;
8 use Girocco::Util;
10 BEGIN {
11 eval {
12 require Digest::SHA;
13 Digest::SHA->import(
14 qw(sha1_hex)
15 );1} ||
16 eval {
17 require Digest::SHA1;
18 Digest::SHA1->import(
19 qw(sha1_hex)
20 );1} ||
21 eval {
22 require Digest::SHA::PurePerl;
23 Digest::SHA::PurePerl->import(
24 qw(sha1_hex)
25 );1} ||
26 die "One of Digest::SHA or Digest::SHA1 or Digest::SHA::PurePerl "
27 . "must be available\n";
30 sub _passwd_add {
31 my $self = shift;
32 filedb_atomic_append(jailed_file('/etc/passwd'),
33 join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell'));
36 sub _sshkey_path {
37 my $self = shift;
38 '/etc/sshkeys/'.$self->{name};
41 sub _sshkey_load {
42 my $self = shift;
43 open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!";
44 my @keys;
45 my $auth;
46 while (<F>) {
47 chomp;
48 if (/^ssh-(?:dss|rsa) /) {
49 push @keys, $_;
50 } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) {
51 my $expire = $2;
52 $auth = $1 unless (time >= $expire);
55 close F;
56 my $keys = join("\n", @keys); chomp $keys;
57 ($keys, $auth);
60 sub _sshkey_save {
61 my $self = shift;
62 open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!";
63 if (defined($self->{auth}) && $self->{auth}) {
64 my $expire = time + 24 * 3600;
65 print F "# REPOAUTH $self->{auth} $expire\n";
67 print F $self->{keys}."\n";
68 close F;
69 chmod 0664, jailed_file($self->_sshkey_path);
72 # private constructor, do not use
73 sub _new {
74 my $class = shift;
75 my ($name) = @_;
76 Girocco::User::valid_name($name) or die "refusing to create user with invalid name ($name)!";
77 my $proj = { name => $name };
79 bless $proj, $class;
82 # public constructor #0
83 # creates a virtual user not connected to disk record
84 # you can conjure() it later to disk
85 sub ghost {
86 my $class = shift;
87 my ($name) = @_;
88 my $self = $class->_new($name);
89 $self;
92 # public constructor #1
93 sub load {
94 my $class = shift;
95 my ($name) = @_;
97 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
98 while (<F>) {
99 chomp;
100 @_ = split /:/;
101 next unless (shift eq $name);
103 my $self = $class->_new($name);
105 (undef, $self->{uid}, undef, $self->{email}) = @_;
106 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
108 return $self;
110 close F;
111 undef;
114 # public constructor #2
115 sub load_by_uid {
116 my $class = shift;
117 my ($uid) = @_;
119 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
120 while (<F>) {
121 chomp;
122 @_ = split /:/;
123 next unless ($_[2] eq $uid);
125 my $self = $class->_new($_[0]);
127 (undef, undef, $self->{uid}, undef, $self->{email}) = @_;
128 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
130 return $self;
132 close F;
133 undef;
136 # $user may not be in sane state if this returns false!
137 sub cgi_fill {
138 my $self = shift;
139 my ($gcgi) = @_;
140 my $cgi = $gcgi->cgi;
142 $self->{name} = $gcgi->wparam('name');
143 Girocco::User::valid_name($self->{name})
144 or $gcgi->err("Name contains invalid characters.");
146 $self->{email} = $gcgi->wparam('email');
147 valid_email($self->{email})
148 or $gcgi->err("Your email sure looks weird...?");
150 $self->keys_fill($gcgi);
153 sub _trimkeys {
154 my $keys = shift;
155 my @lines = ();
156 foreach (split /\r\n|\r|\n/, $keys) {
157 next if /^[ \t]*$/ || /^[ \t]*#/;
158 push(@lines, $_);
160 return join("\n", @lines);
163 sub keys_fill {
164 my $self = shift;
165 my ($gcgi) = @_;
166 my $cgi = $gcgi->cgi;
168 $self->{keys} = _trimkeys($cgi->param('keys'));
169 length($self->{keys}) <= 4096
170 or $gcgi->err("The list of keys is more than 4kb. Do you really need that much?");
171 foreach (split /\r?\n/, $self->{keys}) {
172 /^ssh-(?:dss|rsa) [0-9A-Za-z+\/=]+ \S+@\S+$/
173 or $gcgi->err(<<EOT);
174 Your ssh key ("$_") appears to have an invalid format
175 (does not start with ssh-dss or ssh-rsa or does not end with <tt>\@</tt>-identifier) -
176 maybe your browser has split a single key onto multiple lines?
180 not $gcgi->err_check;
183 sub keys_save {
184 my $self = shift;
186 $self->_sshkey_save;
189 sub gen_auth {
190 my $self = shift;
192 $self->{auth} = sha1_hex(time . $$ . rand() . $self->{keys});
193 $self->_sshkey_save;
194 $self->{auth};
197 sub del_auth {
198 my $self = shift;
200 delete $self->{auth};
203 sub conjure {
204 my $self = shift;
206 $self->_passwd_add;
207 $self->_sshkey_save;
210 ### static methods
212 sub valid_name {
213 $_ = $_[0];
214 /^[a-zA-Z0-9+._-]+$/;
217 sub does_exist {
218 my ($name) = @_;
219 Girocco::User::valid_name($name) or die "tried to query for user with invalid name $name!";
220 (-e jailed_file("/etc/sshkeys/$name"));
223 sub resolve_uid {
224 my ($name) = @_;
225 $Girocco::Config::chrooted and undef; # TODO for ACLs within chroot
226 scalar(getpwnam($name));