Blame


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