]>
Commit | Line | Data |
---|---|---|
1c7f4f65 DM |
1 | package PVE::LXCSetup::Base; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
168d6b07 DM |
6 | use File::stat; |
7 | use Digest::SHA; | |
8 | use IO::File; | |
9 | use Encode; | |
10 | ||
b9cd9975 | 11 | use PVE::INotify; |
55fa4e09 DM |
12 | use PVE::Tools; |
13 | ||
633a7bd8 | 14 | sub new { |
5b4657d0 | 15 | my ($class, $conf, $rootdir) = @_; |
633a7bd8 | 16 | |
5b4657d0 | 17 | return bless { conf => $conf, rootdir => $rootdir }, $class; |
633a7bd8 | 18 | } |
b9cd9975 | 19 | |
c0eae401 | 20 | sub lookup_dns_conf { |
b9cd9975 DM |
21 | my ($conf) = @_; |
22 | ||
23 | my $nameserver = $conf->{'pve.nameserver'}; | |
24 | my $searchdomains = $conf->{'pve.searchdomain'}; | |
25 | ||
26 | if (!($nameserver && $searchdomains)) { | |
27 | ||
28 | if ($conf->{'pve.test_mode'}) { | |
29 | ||
30 | $nameserver = "8.8.8.8 8.8.8.9"; | |
31 | $searchdomains = "promxox.com"; | |
32 | ||
33 | } else { | |
34 | ||
35 | my $host_resolv_conf = PVE::INotify::read_file('resolvconf'); | |
36 | ||
37 | $searchdomains = $host_resolv_conf->{search}; | |
38 | ||
39 | my @list = (); | |
40 | foreach my $k ("dns1", "dns2", "dns3") { | |
41 | if (my $ns = $host_resolv_conf->{$k}) { | |
42 | push @list, $ns; | |
43 | } | |
44 | } | |
45 | $nameserver = join(' ', @list); | |
46 | } | |
47 | } | |
48 | ||
49 | return ($searchdomains, $nameserver); | |
c0eae401 | 50 | } |
b9cd9975 | 51 | |
c0eae401 | 52 | sub update_etc_hosts { |
e4929e97 | 53 | my ($etc_hosts_data, $hostip, $oldname, $newname, $searchdomains) = @_; |
1c7f4f65 | 54 | |
1c7f4f65 DM |
55 | my $done = 0; |
56 | ||
57 | my @lines; | |
e4929e97 DM |
58 | |
59 | my $extra_names = ''; | |
60 | foreach my $domain (PVE::Tools::split_list($searchdomains)) { | |
61 | $extra_names .= ' ' if $extra_names; | |
62 | $extra_names .= "$newname.$domain"; | |
63 | } | |
1c7f4f65 DM |
64 | |
65 | foreach my $line (split(/\n/, $etc_hosts_data)) { | |
66 | if ($line =~ m/^#/ || $line =~ m/^\s*$/) { | |
67 | push @lines, $line; | |
68 | next; | |
69 | } | |
70 | ||
71 | my ($ip, @names) = split(/\s+/, $line); | |
72 | if (($ip eq '127.0.0.1') || ($ip eq '::1')) { | |
73 | push @lines, $line; | |
74 | next; | |
75 | } | |
c325b32f | 76 | |
1c7f4f65 DM |
77 | my $found = 0; |
78 | foreach my $name (@names) { | |
79 | if ($name eq $oldname || $name eq $newname) { | |
80 | $found = 1; | |
81 | } else { | |
82 | # fixme: record extra names? | |
83 | } | |
84 | } | |
85 | $found = 1 if defined($hostip) && ($ip eq $hostip); | |
86 | ||
87 | if ($found) { | |
88 | if (!$done) { | |
89 | if (defined($hostip)) { | |
c325b32f | 90 | push @lines, "$hostip $extra_names $newname"; |
1c7f4f65 DM |
91 | } else { |
92 | push @lines, "127.0.1.1 $newname"; | |
93 | } | |
94 | $done = 1; | |
95 | } | |
96 | next; | |
97 | } else { | |
98 | push @lines, $line; | |
99 | } | |
100 | } | |
101 | ||
102 | if (!$done) { | |
103 | if (defined($hostip)) { | |
e4929e97 | 104 | push @lines, "$hostip $extra_names $newname"; |
1c7f4f65 DM |
105 | } else { |
106 | push @lines, "127.0.1.1 $newname"; | |
107 | } | |
108 | } | |
109 | ||
1e180f97 DM |
110 | my $found_localhost = 0; |
111 | foreach my $line (@lines) { | |
112 | if ($line =~ m/^127.0.0.1\s/) { | |
113 | $found_localhost = 1; | |
114 | last; | |
115 | } | |
116 | } | |
117 | ||
118 | if (!$found_localhost) { | |
119 | unshift @lines, "127.0.0.1 localhost.localnet localhost"; | |
120 | } | |
121 | ||
1c7f4f65 DM |
122 | $etc_hosts_data = join("\n", @lines) . "\n"; |
123 | ||
124 | return $etc_hosts_data; | |
c0eae401 | 125 | } |
1c7f4f65 | 126 | |
142444d5 DM |
127 | sub template_fixup { |
128 | my ($self, $conf) = @_; | |
129 | ||
130 | # do nothing by default | |
131 | } | |
132 | ||
c325b32f | 133 | sub set_dns { |
633a7bd8 | 134 | my ($self, $conf) = @_; |
c325b32f | 135 | |
c0eae401 | 136 | my ($searchdomains, $nameserver) = lookup_dns_conf($conf); |
c325b32f | 137 | |
5b4657d0 | 138 | my $rootdir = $self->{rootdir}; |
c325b32f | 139 | |
5b4657d0 | 140 | my $filename = "$rootdir/etc/resolv.conf"; |
c325b32f DM |
141 | |
142 | my $data = ''; | |
143 | ||
144 | $data .= "search " . join(' ', PVE::Tools::split_list($searchdomains)) . "\n" | |
145 | if $searchdomains; | |
146 | ||
147 | foreach my $ns ( PVE::Tools::split_list($nameserver)) { | |
148 | $data .= "nameserver $ns\n"; | |
149 | } | |
150 | ||
151 | PVE::Tools::file_set_contents($filename, $data); | |
152 | } | |
153 | ||
1c7f4f65 | 154 | sub set_hostname { |
633a7bd8 | 155 | my ($self, $conf) = @_; |
1c7f4f65 | 156 | |
e4929e97 | 157 | my $hostname = $conf->{'lxc.utsname'} || 'localhost'; |
1c7f4f65 DM |
158 | |
159 | $hostname =~ s/\..*$//; | |
160 | ||
5b4657d0 | 161 | my $rootdir = $self->{rootdir}; |
1c7f4f65 | 162 | |
5b4657d0 | 163 | my $hostname_fn = "$rootdir/etc/hostname"; |
1c7f4f65 | 164 | |
e4929e97 | 165 | my $oldname = PVE::Tools::file_read_firstline($hostname_fn) || 'localhost'; |
1c7f4f65 | 166 | |
5b4657d0 | 167 | my $hosts_fn = "$rootdir/etc/hosts"; |
1c7f4f65 DM |
168 | my $etc_hosts_data = ''; |
169 | ||
170 | if (-f $hosts_fn) { | |
171 | $etc_hosts_data = PVE::Tools::file_get_contents($hosts_fn); | |
172 | } | |
173 | ||
c325b32f DM |
174 | my ($ipv4, $ipv6) = PVE::LXC::get_primary_ips($conf); |
175 | my $hostip = $ipv4 || $ipv6; | |
b9cd9975 | 176 | |
c0eae401 | 177 | my ($searchdomains) = lookup_dns_conf($conf); |
b9cd9975 | 178 | |
c0eae401 DM |
179 | $etc_hosts_data = update_etc_hosts($etc_hosts_data, $hostip, $oldname, |
180 | $hostname, $searchdomains); | |
b9cd9975 | 181 | |
1c7f4f65 DM |
182 | PVE::Tools::file_set_contents($hostname_fn, "$hostname\n"); |
183 | PVE::Tools::file_set_contents($hosts_fn, $etc_hosts_data); | |
184 | } | |
185 | ||
55fa4e09 | 186 | sub setup_network { |
633a7bd8 | 187 | my ($self, $conf) = @_; |
55fa4e09 DM |
188 | |
189 | die "please implement this inside subclass" | |
190 | } | |
191 | ||
d66768a2 | 192 | sub setup_init { |
633a7bd8 | 193 | my ($self, $conf) = @_; |
1c7f4f65 | 194 | |
d66768a2 DM |
195 | die "please implement this inside subclass" |
196 | } | |
197 | ||
168d6b07 DM |
198 | my $replacepw = sub { |
199 | my ($file, $user, $epw) = @_; | |
200 | ||
201 | my $tmpfile = "$file.$$"; | |
202 | ||
203 | eval { | |
204 | my $src = IO::File->new("<$file") || | |
205 | die "unable to open file '$file' - $!"; | |
206 | ||
207 | my $st = File::stat::stat($src) || | |
208 | die "unable to stat file - $!"; | |
209 | ||
210 | my $dst = IO::File->new(">$tmpfile") || | |
211 | die "unable to open file '$tmpfile' - $!"; | |
212 | ||
213 | # copy owner and permissions | |
214 | chmod $st->mode, $dst; | |
215 | chown $st->uid, $st->gid, $dst; | |
216 | ||
217 | while (defined (my $line = <$src>)) { | |
218 | $line =~ s/^${user}:[^:]*:/${user}:${epw}:/; | |
219 | print $dst $line; | |
220 | } | |
221 | ||
222 | $src->close() || die "close '$file' failed - $!\n"; | |
223 | $dst->close() || die "close '$tmpfile' failed - $!\n"; | |
224 | }; | |
225 | if (my $err = $@) { | |
226 | unlink $tmpfile; | |
227 | } else { | |
228 | rename $tmpfile, $file; | |
229 | unlink $tmpfile; # in case rename fails | |
230 | } | |
231 | }; | |
232 | ||
233 | sub set_user_password { | |
633a7bd8 | 234 | my ($self, $conf, $user, $opt_password) = @_; |
168d6b07 | 235 | |
5b4657d0 | 236 | my $rootdir = $self->{rootdir}; |
168d6b07 | 237 | |
5b4657d0 | 238 | my $pwfile = "$rootdir/etc/passwd"; |
168d6b07 DM |
239 | |
240 | return if ! -f $pwfile; | |
241 | ||
5b4657d0 | 242 | my $shadow = "$rootdir/etc/shadow"; |
168d6b07 DM |
243 | |
244 | if (defined($opt_password)) { | |
245 | if ($opt_password !~ m/^\$/) { | |
246 | my $time = substr (Digest::SHA::sha1_base64 (time), 0, 8); | |
247 | $opt_password = crypt(encode("utf8", $opt_password), "\$1\$$time\$"); | |
248 | }; | |
249 | } else { | |
250 | $opt_password = '*'; | |
251 | } | |
252 | ||
253 | if (-f $shadow) { | |
254 | &$replacepw ($shadow, $user, $opt_password); | |
255 | &$replacepw ($pwfile, $user, 'x'); | |
256 | } else { | |
257 | &$replacepw ($pwfile, $user, $opt_password); | |
258 | } | |
259 | } | |
260 | ||
4727bd09 DM |
261 | my $randomize_crontab = sub { |
262 | my ($self, $conf) = @_; | |
263 | ||
264 | my $rootdir = $self->{rootdir}; | |
265 | ||
b5e62cd0 DM |
266 | my @files; |
267 | # Note: dir_glob_foreach() untaints filenames! | |
268 | my $cron_dir = "$rootdir/etc/cron.d"; | |
269 | PVE::Tools::dir_glob_foreach($cron_dir, qr/[A-Z\-\_a-z0-9]+/, sub { | |
270 | my ($name) = @_; | |
271 | push @files, "$cron_dir/$name"; | |
272 | }); | |
4727bd09 DM |
273 | |
274 | my $crontab_fn = "$rootdir/etc/crontab"; | |
275 | unshift @files, $crontab_fn if -f $crontab_fn; | |
276 | ||
277 | foreach my $filename (@files) { | |
278 | my $data = PVE::Tools::file_get_contents($filename); | |
279 | my $new = ''; | |
280 | foreach my $line (split(/\n/, $data)) { | |
281 | # we only randomize minutes for root crontab entries | |
282 | if ($line =~ m/^\d+(\s+\S+\s+\S+\s+\S+\s+\S+\s+root\s+\S.*)$/) { | |
283 | my $rest = $1; | |
284 | my $min = int(rand()*59); | |
285 | $new .= "$min$rest\n"; | |
286 | } else { | |
287 | $new .= "$line\n"; | |
288 | } | |
289 | } | |
290 | PVE::Tools::file_set_contents($filename, $new); | |
291 | } | |
292 | }; | |
293 | ||
7ee31468 DM |
294 | sub rewrite_ssh_host_keys { |
295 | my ($self, $conf) = @_; | |
296 | ||
297 | my $rootdir = $self->{rootdir}; | |
298 | ||
299 | my $etc_ssh_dir = "$rootdir/etc/ssh"; | |
300 | ||
301 | return if ! -d $etc_ssh_dir; | |
302 | ||
303 | my $keynames = { | |
304 | rsa1 => 'ssh_host_key', | |
305 | rsa => 'ssh_host_rsa_key', | |
306 | dsa => 'ssh_host_dsa_key', | |
307 | ecdsa => 'ssh_host_ecdsa_key', | |
308 | ed25519 => 'ssh_host_ed25519_key', | |
309 | }; | |
310 | ||
311 | my $hostname = $conf->{'lxc.utsname'} || 'localhost'; | |
312 | $hostname =~ s/\..*$//; | |
313 | ||
314 | foreach my $keytype (keys %$keynames) { | |
315 | my $basename = $keynames->{$keytype}; | |
316 | unlink "${etc_ssh_dir}/$basename"; | |
317 | unlink "${etc_ssh_dir}/$basename.pub"; | |
318 | print "Creating SSH host key '$basename' - this may take some time ...\n"; | |
319 | my $cmd = ['ssh-keygen', '-q', '-f', "${etc_ssh_dir}/$basename", '-t', $keytype, | |
320 | '-N', '', '-C', "root\@$hostname"]; | |
321 | PVE::Tools::run_command($cmd); | |
322 | } | |
323 | } | |
324 | ||
d66768a2 | 325 | sub pre_start_hook { |
633a7bd8 | 326 | my ($self, $conf) = @_; |
d66768a2 | 327 | |
633a7bd8 DM |
328 | $self->setup_init($conf); |
329 | $self->setup_network($conf); | |
330 | $self->set_hostname($conf); | |
331 | $self->set_dns($conf); | |
d66768a2 DM |
332 | |
333 | # fixme: what else ? | |
334 | } | |
335 | ||
336 | sub post_create_hook { | |
633a7bd8 | 337 | my ($self, $conf, $root_password) = @_; |
d66768a2 | 338 | |
142444d5 | 339 | $self->template_fixup($conf); |
4727bd09 DM |
340 | |
341 | &$randomize_crontab($self, $conf); | |
342 | ||
633a7bd8 DM |
343 | $self->set_user_password($conf, 'root', $root_password); |
344 | $self->setup_init($conf); | |
345 | $self->setup_network($conf); | |
346 | $self->set_hostname($conf); | |
347 | $self->set_dns($conf); | |
7ee31468 | 348 | $self->rewrite_ssh_host_keys($conf); |
168d6b07 | 349 | |
55fa4e09 | 350 | # fixme: what else ? |
1c7f4f65 DM |
351 | } |
352 | ||
353 | 1; |