This file is indexed.

/usr/share/opendnssec/convert_database.pl is in opendnssec-enforcer 1:1.4.3-3.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
#!/usr/bin/env perl
#
# $Id: $
# 
# Copyright (c) 2012 OpenDNSSEC AB (svb). All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict;
use warnings;

use DBI;
use Getopt::Long ();
use Pod::Usage ();
use File::Basename ();

# Try to require Term::ReadKey, ignore if it does not exist
eval {
    require Term::ReadKey;
};

my ($sth, $row);

my $schema_path;
my $from;
my $from_username;
my $from_password;
my $to;
my $to_username;
my $to_password;
my $help;

Pod::Usage::pod2usage(1) unless (@ARGV);
Getopt::Long::GetOptions(
    'help|?' => \$help,
    'schema-path:s' => \$schema_path,
    'from=s' => \$from,
    'from-username:s' => \$from_username,
    'from-password:s' => \$from_password,
    'to=s' => \$to,
    'to-username:s' => \$to_username,
    'to-password:s' => \$to_password
) or Pod::Usage::pod2usage(2);
Pod::Usage::pod2usage(-verbose => 99, -exitval => 1) if $help;

#
# Check schema path and existence of database creation scripts
#

unless (defined $schema_path) {
    $schema_path = File::Basename::dirname($0);
}

unless (-r $schema_path.'/database_create.sqlite3' and -r $schema_path.'/database_create.mysql') {
    print STDERR $0, ': Can not find database creation schemas, please set or correct --schema-path.', "\n";
    exit(-1);
}

#
# Check --from, let DBI parse it and check that the correct data sources are used
#

unless (defined $from) {
    print STDERR $0, ': Missing required option --from.', "\n";
    exit(-1);
}

my (undef, $from_data_source) = DBI->parse_dsn($from);

unless (defined $from_data_source and ($from_data_source eq 'mysql' or $from_data_source eq 'SQLite')) {
    print STDERR $0, ': Invalid data source used in --from DSN, only mysql or SQLite allowed (case sensitive).', "\n";
    exit(-1);
}

#
# Check --to, let DBI parse it and check that the correct data sources are used
#

unless (defined $to) {
    print STDERR $0, ': Missing required option --to.', "\n";
    exit(-1);
}

my (undef, $to_data_source) = DBI->parse_dsn($to);

unless (defined $to_data_source and ($to_data_source eq 'mysql' or $to_data_source eq 'SQLite')) {
    print STDERR $0, ': Invalid data source used in --to DSN, only mysql or SQLite allowed (case sensitive).', "\n";
    exit(-1);
}

#
# Prompt for --from-password if from DSN is MySQL and password not given
#

if (!defined $from_password and $from_data_source eq 'mysql') {
    print 'Enter exporting database password (--from-password): ';

    # Try Term::ReadKey
    eval {
        ReadMode('noecho');
        $from_password = ReadLine(0);
        ReadMode('echo');
    };
    
    # Fallback if Term::ReadKey does not exist
    if ($@) {
        $from_password = <STDIN>;
    }
    
    unless ($from_password) {
        print STDERR $0, ': No password given for exporting database.', "\n";
    }

    $from_password =~ s/[\r\n]+$//o;
}

#
# Connect to exporting database and get the schema version
#

my $from_dbh;
my $from_version;

unless ($from_dbh = DBI->connect($from, $from_username, $from_password, { AutoCommit => 1 })) {
    print STDERR $0, ': Unable to connect to exporting database [', $from, ']: ', $DBI::errstr, "\n";
    exit(-1);
}
print 'Connected to exporting database ', $from, "\n";

$sth = prepare_or_die($from_dbh, 'SELECT version FROM dbadmin');
unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($from_version = $row->{version})) {
    $sth->finish;
    print STDERR $0, ': Unable to get schema version from exporting database', "\n";
    exit(-1);
}
$sth->finish;

#
# Check for supported database schema version
#

my $from_version_valid = 0;
if ($from_version == 3) {
    $from_version_valid = 1;
}

unless ($from_version_valid) {
    print STDERR $0, ': Exporting database schema version is not supported for conversion.', "\n";
    exit(-1);
}

#
# Validate existing data depending on importing data source
#

if ($to_data_source eq 'mysql') {
    if ($from_version == 3) {
        my $valid = 1;
        print 'Validating existing data', "\n";
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM securitymodules WHERE id > 127');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table securitymodules', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table securitymodules field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM categories WHERE id > 127');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table categories', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table categories field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM parameters WHERE id > 8388607');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table parameters', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table parameters field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM serialmodes WHERE id > 127');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table serialmodes', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table serialmodes field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM policies WHERE id > 8388607');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table policies', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table policies field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM zones WHERE id > 8388607');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table zones', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table zones field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        $sth = prepare_or_die($from_dbh, 'SELECT COUNT(*) AS "count" FROM parameters_policies WHERE id > 8388607');
        unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($row->{count})) {
            $sth->finish;
            print STDERR $0, ': Unable to validate table parameters_policies', "\n";
            exit(-1);
        }
        if ($row->{count}) {
            print 'Table parameters_policies field id contains too large values', "\n";
            $valid = 0;
        }
        $sth->finish;
        
        unless ($valid) {
            print STDERR $0, ': Unable to get schema version from exporting database', "\n";
            exit(-1);
        }
    }
}

#
# Ask the user if he really wants to create the importing database and delete existing data
#

print 'Create the importing database, this will delete existing data? [NO/yes] ';
my $answer = <STDIN>;
chomp($answer);
unless ($answer =~ /^yes$/io) {
    exit(0);
}

if ($to_data_source eq 'mysql') {
    $to .= ';mysql_multi_statements=1';
}

#
# Prompt for --to-password if from DSN is MySQL and password not given
#

if (!defined $to_password and $to_data_source eq 'mysql') {
    print 'Enter importing database password (--to-password): ';

    # Try Term::ReadKey
    eval {
        ReadMode('noecho');
        $to_password = ReadLine(0);
        ReadMode('echo');
    };
    
    # Fallback if Term::ReadKey does not exist
    if ($@) {
        $to_password = <STDIN>;
    }
    
    unless ($to_password) {
        print STDERR $0, ': No password given for importing database.', "\n";
    }

    $to_password =~ s/[\r\n]+$//o;
}

#
# Connect to importing database
#

my $to_dbh;
my $to_version;

unless ($to_dbh = DBI->connect($to, $to_username, $to_password, { AutoCommit => 1 })) {
    print STDERR $0, ': Unable to connect to importing database [', $to, ']: ', $DBI::errstr, "\n";
    exit(-1);
}
print 'Connected to importing database ', $to, "\n";

#
# Create the importing database tables
#

if ($to_data_source eq 'mysql') {
    unless (open(FILE, $schema_path.'/database_create.mysql')) {
        print STDERR $0, ': ', "\n";
        exit(-1);
    }
    
    my $sql = '';
    while ((my $line = <FILE>)) {
        if ($line =~ /^\s*--/o) {
            next;
        }
        
        $line =~ s/\r//go;
        $sql .= $line;
        
        if ($sql =~ /\;$/o) {
            unless ($to_dbh->do($sql)) {
                print STDERR $0, ': Unable to create importing database, statement "', $sql, '" failed: ', $to_dbh->errstr, "\n";
                exit(-1);
            }
            $sql = '';
        }
    }
}
elsif ($to_data_source eq 'SQLite') {
    unless (open(FILE, $schema_path.'/database_create.sqlite3')) {
        print STDERR $0, ': ', "\n";
        exit(-1);
    }
    
    my $sql = '';
    while ((my $line = <FILE>)) {
        if ($line =~ /^\s*--/o) {
            next;
        }
        
        $line =~ s/\r//go;
        $sql .= $line;
        
        if ($sql =~ /\;$/o) {
            unless ($to_dbh->do($sql)) {
                print STDERR $0, ': Unable to create importing database, statement "', $sql, '" failed: ', $to_dbh->errstr, "\n";
                exit(-1);
            }
            $sql = '';
        }
    }
}
else {
    print STDERR $0, ': Invalid data source, internal bug? please report this', "\n";
    exit(-1);
}

#
# Get the schema version of the importing database
#

$sth = prepare_or_die($to_dbh, 'SELECT version FROM dbadmin');
unless ($sth->execute and defined ($row = $sth->fetchrow_hashref) and defined ($to_version = $row->{version})) {
    $sth->finish;
    print STDERR $0, ': Unable to get schema version from importing database', "\n";
    exit(-1);
}
$sth->finish;

#
# Validate that we are using the same exporting and importing schema version
#

unless ($from_version == $to_version) {
    print STDERR $0, ': Database schema version missmatch [from: ', $from_version, ' to: ', $to_version, '], can not convert databases of different schema versions, please upgrade existing installation first.', "\n";
    exit(-1);
}

#
# Convert the database
#

if ($from_version == 3) {
    #
    # Schema version 3 does not need any data modifications so just dump it out and in
    #
    
    my @tables = (
        { securitymodules => {
            delete => 'DELETE FROM securitymodules',
            select => 'SELECT * FROM securitymodules',
            insert => 'INSERT INTO securitymodules VALUES ( ?, ?, ?, ? )'
        }},
        { categories => {
            delete => 'DELETE FROM categories',
            select => 'SELECT * FROM categories',
            insert => 'INSERT INTO categories VALUES ( ?, ? )'
        }},
        { parameters => {
            delete => 'DELETE FROM parameters',
            select => 'SELECT * FROM parameters',
            insert => 'INSERT INTO parameters VALUES ( ?, ?, ?, ? )'
        }},
        { serialmodes => {
            delete => 'DELETE FROM serialmodes',
            select => 'SELECT * FROM serialmodes',
            insert => 'INSERT INTO serialmodes VALUES ( ?, ?, ? )'
        }},
        { policies => {
            delete => 'DELETE FROM policies',
            select => 'SELECT * FROM policies',
            insert => 'INSERT INTO policies VALUES ( ?, ?, ?, ?, ?, ? )'
        }},
        { zones => {
            delete => 'DELETE FROM zones',
            select => 'SELECT * FROM zones',
            insert => 'INSERT INTO zones VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )'
        }},
        { keypairs => {
            delete => 'DELETE FROM keypairs',
            select => 'SELECT * FROM keypairs',
            insert => 'INSERT INTO keypairs VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )'
        }},
        { dnsseckeys => {
            delete => 'DELETE FROM dnsseckeys',
            select => 'SELECT * FROM dnsseckeys',
            insert => 'INSERT INTO dnsseckeys VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )'
        }},
        { parameters_policies => {
            delete => 'DELETE FROM parameters_policies',
            select => 'SELECT * FROM parameters_policies',
            insert => 'INSERT INTO parameters_policies VALUES ( ?, ?, ?, ? )'
        }}
    );
    
    print 'Initializing conversion', "\n";
    
    foreach my $table (@tables) {
        my ($name) = keys %$table;
        
        $table->{$name}->{del_sth} = prepare_or_die($to_dbh, $table->{$name}->{delete});
        $table->{$name}->{sel_sth} = prepare_or_die($from_dbh, $table->{$name}->{select});
        $table->{$name}->{ins_sth} = prepare_or_die($to_dbh, $table->{$name}->{insert});
    }
    
    print 'Deleting existing data in importing database', "\n";
    
    foreach my $table (reverse(@tables)) {
        my ($name) = keys %$table;
        
        unless ($table->{$name}->{del_sth}->execute) {
            $table->{$name}->{del_sth}->finish;
            print STDERR $0, ': Unable to delete existing data in importing database table ', $name, ': ', $table->{$name}->{del_sth}->errstr, "\n";
            exit(-1);
        }
        
        $table->{$name}->{del_sth}->finish;
    }
    
    print 'Converting database', "\n";
    
    foreach my $table (@tables) {
        my ($name) = keys %$table;
        
        print "\t", $name, "\n";
        
        unless ($table->{$name}->{sel_sth}->execute) {
            $table->{$name}->{sel_sth}->finish;
            print STDERR $0, ': Unable to select from exporting database table ', $name, ': ', $table->{$name}->{sel_sth}->errstr, "\n";
            exit(-1);
        }
        while (defined (my $row = $table->{$name}->{sel_sth}->fetchrow_arrayref)) {
            unless ($table->{$name}->{ins_sth}->execute(@$row)) {
                $table->{$name}->{ins_sth}->finish;
                print STDERR $0, ': Unable to insert into importing database table ', $name, ': ', $table->{$name}->{ins_sth}->errstr, "\n";
                exit(-1);
            }
            $table->{$name}->{ins_sth}->finish;
        }
        $table->{$name}->{sel_sth}->finish;
    }
    
    print 'Optimizing database', "\n";
    
    if ($to_data_source eq 'mysql') {
        foreach my $table (@tables) {
            my ($name) = keys %$table;
            
            unless ($to_dbh->do('OPTIMIZE TABLE '.$name)) {
                print STDERR $0, ': Unable to OPTIMIZE TABLE ', $name, ': ', $to_dbh->errstr, "\n";
                exit(-1);
            }
        }
    }
    elsif ($to_data_source eq 'SQLite') {
        unless ($to_dbh->do('VACUUM')) {
            print STDERR $0, ': Unable to VACUUM database: ', $to_dbh->errstr, "\n";
            exit(-1);
        }
        
        unless ($to_dbh->do('ANALYZE')) {
            print STDERR $0, ': Unable to ANALYZE database: ', $to_dbh->errstr, "\n";
            exit(-1);
        }
    }
    else {
        print STDERR $0, ': Invalid data source, internal bug? please report this', "\n";
        exit(-1);
    }
}

print 'Done', "\n";

exit 0;

#
# Close connections on exit() if they exist
#

END {
    if (defined $from_dbh) {
        $from_dbh->disconnect;
    }
    if (defined $to_dbh) {
        $to_dbh->disconnect;
    }
}

#
# Prepare a statement or die trying
#

sub prepare_or_die {
    my ($dbh, $statement) = @_;
    my $sth;
    
    unless (defined ($sth = $dbh->prepare($statement))) {
        print STDERR $0, ': Unable to prepare statement "', $statement, '": ', $dbh->errstr, "\n";
        exit(-1);
    }
    
    $sth;
}

__END__

=head1 NAME

convert_database.pl - OpenDNSSEC database conversion tool

=head1 SYNOPSIS

convert_database.pl [options]

=head1 OPTIONS

=over 8

=item B<--schema-path <schema path>>

Specify the path to the directory containing the database schemas. (default to same path as convert_database.pl)

=item B<--from <dsn>>

Specify DBI DSN database connection string to use as the exporting database, see man/perldoc DBD::mysql or DBD::SQLite for more information. (Required)

=item B<--from-username <username>>

Username for the exporting database (default current logged in user).

=item B<--from-password <password>>

Password to the exporting database (default prompted if MySQL).

=item B<--to <dsn>>

Specify DBI DSN database connection string to use as the importing database, see man/perldoc DBD::mysql or DBD::SQLite for more information. (Required)

=item B<--to-username <username>>

Username for the importing database (default current logged in user).

=item B<--to-password <password>>

Password to the importing database (default prompted if MySQL).

=item B<--help>

Print a brief help message and exits.

=back

=head1 DESCRIPTION

This program converts OpenDNSSEC Enforcer database from one backend to another.

=head1 EXAMPLES

This example converts from a SQLite to a MySQL database using default installation paths:

convert_database.pl --from dbi:SQLite:dbname=/var/opendnssec/kasp.db --to dbi:mysql:database=kasp;host=localhost --to-username kasp --to-password kasp

=cut