README: Describe forks
[girocco.git] / Girocco / User.pm
blob2811873b521583e4cbb5a9173595d5785b2be6f1
1 package Girocco::User;
3 use strict;
4 use warnings;
6 use Girocco::Config;
8 BEGIN {
9 use Girocco::CGI;
10 use Girocco::Util;
11 use Digest::SHA1 qw(sha1_hex);
14 sub _passwd_add {
15 my $self = shift;
16 filedb_atomic_append(jailed_file('/etc/passwd'),
17 join(':', $self->{name}, 'x', '\i', 65534, $self->{email}, '/', '/bin/git-shell'));
20 sub _sshkey_path {
21 my $self = shift;
22 '/etc/sshkeys/'.$self->{name};
25 sub _sshkey_load {
26 my $self = shift;
27 open F, "<".jailed_file($self->_sshkey_path) or die "sshkey load failed: $!";
28 my @keys;
29 my $auth;
30 while (<F>) {
31 chomp;
32 if (/^ssh-(?:dss|rsa) /) {
33 push @keys, $_;
34 } elsif (/^# REPOAUTH ([0-9a-f]+) (\d+)/) {
35 my $expire = $2;
36 $auth = $1 unless (time >= $expire);
39 close F;
40 my $keys = join('', @keys); chomp $keys;
41 ($keys, $auth);
44 sub _sshkey_save {
45 my $self = shift;
46 open F, ">".jailed_file($self->_sshkey_path) or die "sshkey failed: $!";
47 if (defined($self->{auth}) && $self->{auth}) {
48 my $expire = time + 24 * 3600;
49 print F "# REPOAUTH $self->{auth} $expire\n";
51 print F $self->{keys}."\n";
52 close F;
53 chmod 0664, jailed_file($self->_sshkey_path);
56 # private constructor, do not use
57 sub _new {
58 my $class = shift;
59 my ($name) = @_;
60 Girocco::User::valid_name($name) or die "refusing to create user with invalid name ($name)!";
61 my $proj = { name => $name };
63 bless $proj, $class;
66 # public constructor #0
67 # creates a virtual user not connected to disk record
68 # you can conjure() it later to disk
69 sub ghost {
70 my $class = shift;
71 my ($name) = @_;
72 my $self = $class->_new($name);
73 $self;
76 # public constructor #1
77 sub load {
78 my $class = shift;
79 my ($name) = @_;
81 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
82 while (<F>) {
83 chomp;
84 @_ = split /:/;
85 next unless (shift eq $name);
87 my $self = $class->_new($name);
89 (undef, $self->{uid}, undef, $self->{email}) = @_;
90 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
92 return $self;
94 close F;
95 undef;
98 # public constructor #2
99 sub load_by_uid {
100 my $class = shift;
101 my ($uid) = @_;
103 open F, jailed_file("/etc/passwd") or die "user load failed: $!";
104 while (<F>) {
105 chomp;
106 @_ = split /:/;
107 next unless ($_[2] eq $uid);
109 my $self = $class->_new($_[0]);
111 (undef, undef, $self->{uid}, undef, $self->{email}) = @_;
112 ($self->{keys}, $self->{auth}) = $self->_sshkey_load;
114 return $self;
116 close F;
117 undef;
120 # $user may not be in sane state if this returns false!
121 sub cgi_fill {
122 my $self = shift;
123 my ($gcgi) = @_;
124 my $cgi = $gcgi->cgi;
126 $self->{name} = $gcgi->wparam('name');
127 Girocco::User::valid_name($self->{name})
128 or $gcgi->err("Name contains invalid characters.");
130 $self->{email} = $gcgi->wparam('email');
131 valid_email($self->{email})
132 or $gcgi->err("Your email sure looks weird...?");
134 $self->keys_fill($gcgi);
137 sub keys_fill {
138 my $self = shift;
139 my ($gcgi) = @_;
140 my $cgi = $gcgi->cgi;
142 $self->{keys} = $cgi->param('keys');
143 length($self->{keys}) <= 4096
144 or $gcgi->err("The list of keys is more than 4kb. Do you really need that much?");
145 foreach (split /\r?\n/, $self->{keys}) {
146 /^ssh-(?:dss|rsa) .* \S+@\S+$/ or $gcgi->err("Your ssh key (\"$_\") appears to have invalid format (does not start by ssh-dss|rsa or does not end with @-identifier) - maybe your browser has split a single key to multiple lines?");
149 not $gcgi->err_check;
152 sub keys_save {
153 my $self = shift;
155 $self->_sshkey_save;
158 sub gen_auth {
159 my $self = shift;
161 $self->{auth} = Digest::SHA1::sha1_hex(time . $$ . rand() . $self->{keys});
162 $self->_sshkey_save;
163 $self->{auth};
166 sub del_auth {
167 my $self = shift;
169 delete $self->{auth};
172 sub conjure {
173 my $self = shift;
175 $self->_passwd_add;
176 $self->_sshkey_save;
179 ### static methods
181 sub valid_name {
182 $_ = $_[0];
183 /^[a-zA-Z0-9+._-]+$/;
186 sub does_exist {
187 my ($name) = @_;
188 Girocco::User::valid_name($name) or die "tried to query for user with invalid name $name!";
189 (-e jailed_file("/etc/sshkeys/$name"));
192 sub resolve_uid {
193 my ($name) = @_;
194 $Girocco::Config::chrooted and undef; # TODO for ACLs within chroot
195 scalar(getpwnam($name));