Blob


1 #!/usr/bin/perl
3 use strict;
4 use warnings;
5 use OpenBSD::Pledge;
6 use OpenBSD::Unveil;
7 #use Data::Dumper;
8 use File::Copy qw(copy);
9 use Getopt::Std;
11 my $vmconfpath = "/etc/vm.conf";
12 my $zonedir = "/var/nsd/zones/master/";
13 my $hostname = "host.lecturify.net";
14 my $ipv4path = "/etc/ipv4s";
15 my $isopath = "/home/iso/install73.iso";
17 # Read from filename and return array of lines without trailing newlines
18 sub readarray {
19 my ($filename) = @_;
20 open(my $fh, '<', $filename) or die "Could not read file '$filename' $!";
21 chomp(my @lines = <$fh>);
22 close $fh;
23 return @lines;
24 }
26 # Read from filename and return as string
27 sub readstr {
28 my ($filename) = @_;
29 open my $fh, '<', $filename or die "Could not read file '$filename' $!";
30 my $str = do { local $/; <$fh> };
31 close $fh;
32 return $str;
33 }
35 # Write str to filename
36 sub writefile {
37 my ($filename, $str) = @_;
38 open(my $fh, '>', "$filename") or die "Could not write to $filename";
39 print $fh $str;
40 close $fh;
41 }
43 # Append str to filename
44 sub appendfile {
45 my ($filename, $str) = @_;
46 open(my $fh, '>>', "$filename") or die "Could not append to $filename";
47 print $fh $str;
48 close $fh;
49 }
51 sub date {
52 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
53 my $localtime = sprintf("%04d%02d%02d", $year+1900, $mon+1, $mday);
54 return $localtime;
55 }
57 # Create DNS RR given name, TTL, record type, and value
58 sub setdns {
59 my ($name, $ttl, $rr, $value) = @_;
60 my $zonepath = "$zonedir/$hostname";
61 `doas chmod -R g+w $zonedir`;
62 my @lines = readarray($zonepath);
63 foreach my $line (@lines) {
64 # increment the zone's serial number
65 if ($line =~ /(\d{8})(\d{2})((\s+\d+){4}\s*\))/) {
66 my $date = date();
67 my $serial = 0;
68 if ($date <= $1) { $serial = $2+1; }
69 $line = $`.$date.sprintf("%02d",$serial).$3.$';
70 }
71 }
72 push(@lines, "$name $ttl IN $rr $value");
73 # trailing newline necessary
74 writefile("$zonepath.bak", join("\n", @lines)."\n");
75 copy "$zonepath.bak", $zonepath;
76 if (system("doas -u _nsd nsd-control reload")) {
77 return 0;
78 } else {
79 return 1;
80 }
81 }
83 # Return the next IPv4 address and update ipv4path
85 sub nextipv4 {
86 my @ipv4s;
87 if (!(-s "$ipv4path")) {
88 print "No IPv4 addresses in $ipv4path!\n";
89 die;
90 } else {
91 @ipv4s = readarray($ipv4path);
92 }
93 my $ipv4 = shift(@ipv4s);
94 system "doas chmod g+w $ipv4path";
95 writefile($ipv4path, join("\n", @ipv4s));
96 return $ipv4;
97 }
99 sub ipv6fromipv4 {
100 my ($ipv4) = @_;
101 if ($ipv4 =~ /^[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)$/) {
102 return "2602:fccf:1:1".sprintf("%03d",$1)."::";
106 sub deletedns {
107 my ($username) = @_;
108 my $filename = "$zonedir/$hostname";
109 `doas chmod -R g+w $zonedir`;
110 my @buf = grep(!/^;?(ns[0-9]\.)?$username\s/, readarray($filename));
111 writefile("${filename}.bak", join("\n", @buf)."\n");
112 copy "${filename}.bak", "$filename";
115 sub createvm {
116 my ($username, $password) = @_;
117 print "Username: $username\n";
118 print "Password: $password\n";
119 system "doas groupadd $username";
120 system "doas adduser -batch $username $username $username `encrypt $password`";
121 system "doas usermod -G vmdusers $username";
122 system "doas chmod -R o-rwx /home/$username";
123 system "doas ln -s $isopath /home/$username/$username.iso";
124 system "doas su -l $username -c \"vmctl create -s 20G $username.qcow2\"";
125 print "VM created for $username!\n";
126 my @vmconf = readarray($vmconfpath);
127 my $lladdr;
128 foreach my $line (@vmconf) {
129 if ($line =~ /lladdr (.*)/) {
130 $lladdr = $1;
133 if (defined($lladdr) && $lladdr =~ /([0-9a-fA-F]{2})$/) {
134 $lladdr = $`.($1+1);
136 my $block = <<"EOF";
137 vm "$username" {
138 owner $username
139 memory 1024M
140 cdrom "/home/$username/$username.iso"
141 disk /home/$username/$username.qcow2
142 interface {
143 locked lladdr $lladdr
144 switch "switch0"
147 EOF
148 appendfile($vmconfpath, $block);
149 `doas vmctl reload`;
152 sub createconf {
153 my ($username, $password, $ipv4, $ipv6) = @_;
154 my $block = readstr("vmmmgr.conf");
155 $block =~ s/\$password/$password/g;
156 $block =~ s/\$username/$username/g;
157 $block =~ s/\$ipv4/$ipv4/g;
158 $block =~ s/\$ipv6/$ipv6/g;
159 writefile("/var/www/htdocs/$username-install.conf", "$block");
162 sub deletevm {
163 my ($username) = @_;
164 print "$username deleted";
165 my $buf = readstr($vmconfpath);
166 my $vmconf;
167 if ($buf =~ /vm "$username" \{.*\n\}/s) {
168 $vmconf = $`.$';
169 print $vmconf;
171 system "doas vmctl stop $username";
172 system "doas userdel -r $username";
173 system "doas groupdel $username";
174 system("doas touch ${vmconfpath}.bak");
175 system("doas chmod g+w ${vmconfpath}.bak");
176 writefile("${vmconfpath}.bak", "$vmconf");
177 copy "${vmconfpath}.bak", "$vmconfpath";
178 print "VM deleted for $username!\n";
179 system "doas vmctl reload";
182 sub mail {
183 my ($username, $password, $ipv4, $ipv6) = @_;
184 my $ircserver = "irc.lecturify.net";
185 my $ircchannel = "#wheel";
186 my $fromname = 'jrmu';
187 my $from = 'jrmu@ircnow.org';
188 my $sshfingerprints =
189 "ssh-keygen -E md5 -lf /etc/ssh/ssh_host_dsa_key.pub
190 ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ecdsa_key.pub
191 ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ed25519_key.pub
192 ssh-keygen -E md5 -lf /etc/ssh/ssh_host_rsa_key.pub
193 ssh-keygen -lf /etc/ssh/ssh_host_dsa_key.pub
194 ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub
195 ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
196 ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub";
198 foreach my $line (readarray("vpses")) {
199 if ($line =~ /^(.*) (.*)$/) {
200 print "\$ ssh $1\@$1.$hostname , password $2\n";
203 my $line = readstr("mailtemplate");
204 $line =~ s/\$username/$username/g;
205 $line =~ s/\$password/$password/g;
206 $line =~ s/\$hostname/$hostname/g;
207 $line =~ s/\$ircserver/$ircserver/g;
208 $line =~ s/\$ircchannel/$ircchannel/g;
209 $line =~ s/\$ipv4/$ipv4/g;
210 $line =~ s/\$ipv6/$ipv6/g;
211 $line =~ s/\$sshfingerprints/$sshfingerprints/g;
212 open(my $fh, "| /usr/sbin/sendmail -tv -F '$fromname' -f $from") or die "Could not send mail $!";
213 print $fh $line;
214 close $fh;
215 return "true";
218 my %opts;
219 getopts('hid', \%opts);
220 if (defined($opts{i})) {
221 print "Username: ";
222 chomp(my $username = <STDIN>);
223 print "Password: ";
224 chomp (my $password = <STDIN>);
225 print "IPv4: ";
226 chomp (my $ipv4 = <STDIN>);
227 $ipv4 = $ipv4 // nextipv4();
228 print "IPv6: ";
229 chomp (my $ipv6 = <STDIN>);
230 $ipv6 = $ipv6 // ipv6fromipv4($ipv4);
231 setdns($username, "3600", "A", $ipv4);
232 setdns($username, "3600", "AAAA", $ipv6);
233 setdns("ns1.$username", "3600", "A", $ipv4);
234 setdns("ns2.$username", "3600", "A", $ipv4);
235 setdns(";$username", "3600", "NS", "ns1.$username");
236 setdns(";$username", "3600", "NS", "ns2.$username");
237 if (system("./vmmmgr-get-sets")) { # return >0 on error
238 die "OpenBSD installation sets corrupt";
239 } else { # return 0 on success
240 createvm($username, $password);
241 createconf($username, $password, $ipv4, $ipv6);
243 } elsif (defined($opts{d})) {
244 print "Username: ";
245 chomp(my $username = <STDIN>);
246 deletevm($username);
247 deletedns($username);
248 } else {
249 print "Usage: vmmmgr -dhi\n"