~drizzle-trunk/drizzle/development

1 by brian
clean slate
1
#!@PERL@
2
# ****************************
3
package MySQLaccess;
4
#use strict;
5
use File::Temp qw(tempfile tmpnam);
6
use Fcntl;
7
8
BEGIN {
9
	# ****************************
10
	# static information...
11
	$VERSION     = "2.06, 20 Dec 2000";
12
	$0           =~ m%/([^/]+)$%o;
13
	$script      = $1;
14
        $script      = 'MySQLAccess' unless $script;
15
	$script_conf = "$script.conf";
16
	$script_log  = $ENV{'HOME'}."/$script.log";
17
18
	# ****************************
19
	# information on MySQL
20
	$MYSQL     = '@bindir@/mysql';    # path to mysql executable
21
	$SERVER    = '3.21';
22
	$MYSQL_OPT = ' --batch --unbuffered';
23
	$ACCESS_DB = 'mysql';		 # name of DB with grant-tables
24
	$ACCESS_H  = 'host';		 # 
25
	$ACCESS_U  = 'user';	         # 
26
	$ACCESS_D  = 'db';               #
27
	# Add/Edit privileges
28
	$ACCESS_H_TMP = 'host_tmp';      
29
	$ACCESS_U_TMP = 'user_tmp';      
30
	$ACCESS_D_TMP = 'db_tmp';        
31
	$ACCESS_H_BCK = 'host_backup';   
32
	$ACCESS_U_BCK = 'user_backup';   
33
	$ACCESS_D_BCK = 'db_backup';     
34
        $DIFF      = '/usr/bin/diff'; 
35
        $MYSQLDUMP = '@bindir@/mysqldump';
36
                                         #path to mysqldump executable
37
38
        $MYSQLADMIN= 'http://foobar.com/MySQLadmin';
39
                                         #URL of CGI for manipulating
40
                                         #the temporary grant-tables
41
}
42
43
END {
44
	unlink $MYSQL_CNF if defined $MYSQL_CNF and not $DEBUG;
45
}
46
47
$INFO = <<"_INFO";
48
--------------------------------------------------------------------------
49
   mysqlaccess (Version $VERSION)
50
   ~~~~~~~~~~~
51
   Copyright (C) 1997,1998 Yves.Carlier\@rug.ac.be
52
                           University of Ghent (RUG), Belgium
53
                           Administratieve Informatieverwerking (AIV)
54
55
   report the access-privileges for a USER from a HOST to a DB
56
57
   Many thanks go to <monty\@mysql.com> and <psmith\@BayNetworks.COM>
58
   for their suggestions, debugging and patches. 
59
60
   use `$script -?' to get more information on available options.
61
62
   From version 2.0x, $script can also be used through a WEB-browser
63
   if it is ran as a CGI-script.  (See the release-notes)
64
65
--------------------------------------------------------------------------
66
_INFO
67
68
$OPTIONS = <<_OPTIONS;
69
70
Usage: $script [host [user [db]]] OPTIONS
71
72
  -?, --help           display this helpscreen and exit
73
  -v, --version        print information on the program `$script'
74
75
  -u, --user=#         username for logging in to the db
76
  -p, --password=#     validate password for user
77
  -h, --host=#         name or IP-number of the host
78
  -d, --db=#           name of the database
79
80
  -U, --superuser=#    connect as superuser
81
  -P, --spassword=#    password for superuser
82
  -H, --rhost=#        remote MySQL-server to connect to
83
      --old_server     connect to old MySQL-server (before v3.21) which 
84
                       does not yet know how to handle full where clauses.
85
86
  -b, --brief          single-line tabular report
87
  -t, --table          report in table-format
88
89
  --relnotes           print release-notes
90
  --plan               print suggestions/ideas for future releases
91
  --howto              some examples of how to run `$script'
92
  --debug=N            enter debuglevel N (0..3)
93
94
  --copy               reload temporary grant-tables from original ones
95
  --preview            show differences in privileges after making
96
                       changes in (temporary) grant-tables
97
  --commit             copy grant-rules from temporary tables to grant-tables
98
                       (!don't forget to do an mysqladmin reload)
99
  --rollback           undo the last changes to the grant-tables.
100
101
  Note:
102
    + At least the user and the db must be given (even with wildcards)
103
    + If no host is given, `localhost' is assumed
104
    + Wilcards (*,?,%,_) are allowed for host, user and db, but be sure 
105
      to escape them from your shell!! (ie type \\* or '*')
106
_OPTIONS
107
108
$RELEASE = <<'_RELEASE';
109
 
110
Release Notes:
111
-------------
112
  0.1-beta1: internal
113
  - first trial.
114
 
115
  0.1-beta2: (1997-02-27)
116
  - complete rewrite of the granting-rules, based on the documentation
117
    found in de FAQ.
118
  - IP-number and name for a host are equiv.
119
 
120
  0.1-beta3: (1997-03-10)
121
  - more information
122
  - 'localhost' and the name/ip of the local machine are now equiv.
123
124
  0.1-beta4: (1997-03-11)
125
  - inform the user if he has not enough priv. to read the mysql db
126
127
  1.0-beta1: (1997-03-12)
128
  suggestions by Monty:
129
  - connect as superuser with superpassword.
130
  - mysqlaccess could also notice if all tables are empty. This means
131
    that all user have full access!
132
  - It would be nice if one could optionally start mysqlaccess without
133
    any options just the arguments 'user db' or 'host user db', where
134
    host is 'localhost' if one uses only two arguments.
135
136
  1.0-beta2: (1997-03-14)
137
  - bugfix: translation to reg.expr of \_ and \%.
138
  - bugfix: error in matching regular expression and string given
139
            by user which resulted in
140
            'test_123' being matched with 'test'
141
142
  1.0-beta3: (1997-03-14)
143
  - bugfix: the user-field should not be treated as a sql-regexpr,
144
            but as a plain string.
145
  - bugfix: the host-table should not be used if the host isn't empty in db
146
                                          or  if the host isn't emty in user
147
            (Monty)
148
 
149
  1.0-beta4: (1997-03-14)
150
  - bugfix: in an expression "$i = $j or $k", the '=' binds tighter than the or
151
            which results in problems...
152
            (by Monty)
153
  - running mysqlaccess with "perl -w" gives less warnings...   ;-)
154
155
  1.0-beta5: (1997-04-04)
156
  - bugfix: The table sorting was only being applied to the "user" table; all
157
            the tables need to be sorted.  Rewrote the sort algorithm, and
158
            the table walk algorithm (no temp file anymore), and various
159
            other cleanups.  I believe the access calculation is 100% correct.
160
            (by Paul D. Smith <psmith\@baynetworks.com>)
161
  - Allow the debug level to be set on the cmd line with --debug=N.
162
            (by Paul D. Smith <psmith\@baynetworks.com>)
163
  - More -w cleanups; should be totally -w-clean.
164
            (by Paul D. Smith <psmith\@baynetworks.com>)
165
 
166
  1.1-beta1: (1997-04-xx) 
167
  1.1-beta2: (1997-04-11)
168
  - new options:
169
             --all_users : report access-rights for all possible users
170
             --all_dbs   : report access-rights for all possible dbs
171
             --all_hosts : report access-rights for all possible hosts
172
             --brief     : as brief as possible, don't mention notes,warnings and rules
173
             --password  : validate password for user 
174
  - layout: long messages are wrapped on the report.
175
  - functionality:
176
            more descriptive notes and warnings
177
            wildcards (*,?) are allowed in the user,host and db options
178
            setting xxxx=* is equiv to using option --all_xxxx
179
            note: make sure you escape your wildcards, so they don't get
180
                  interpreted by the shell.  use \* or '*'
181
  - bugfix: Fieldnames which should be skipped on the output can now have
182
            a first capital letter.
183
  - bugfix: any option with a '.' (eg ip-number) was interpreted as
184
            a wildcard-expression.
185
  - bugfix: When no entry was found in the db-table, the default accessrights are
186
            N, instead of the faulty Y in a previous version.
187
 
188
  1.1-beta-3  : (1997-04-xx)
189
  1.1-beta-4  : (1997-04-xx)
190
  1.1-beta-5  : (1997-04-xx)
191
  1.1         : (1997-04-28)
192
  - new options:
193
            --rhost     : name of mysql-server to connect to
194
            --plan      : print suggestions/ideas for future releases
195
            --relnotes  : display release-notes
196
            --howto     : display examples on how to use mysqlaccess
197
            --brief     : single-line tabular output
198
  - functionality/bugfix:
199
    *      removed options --all_users,--all_dbs,--all_hosts, which 
200
           were redundant with the wildcard-expressions for the corresponding
201
           options. They made the processing of the commandline too painful 
202
           and confusing ;-)
203
           (suggested by psmith)
204
    *      redefined the option --brief, which now gives a single-line 
205
           tabular output
206
    *      Now we check if the right version of the mysql-client is used,
207
           since we might use an option not yet implemented in an
208
           older version (--unbuffered, since 3.0.18)
209
           Also the error-messages the mysql-client reports are 
210
           better interpreted ;-)  
211
    *      Wildcards can now be given following the SQL-expression 
212
           (%,_) and the Regular-expression (*,?) syntax.
213
  - speed: we now open a bidirectional pipe to the mysql-client, and keep 
214
           it open throughout the whole run. Queries are written to,
215
           and the answers read from the pipe.
216
           (suggested by monty)
217
  - bugfixes:
218
    *      the Rules were not properly reset over iterations 
219
    *      when in different tables the field-names were not identical, 
220
           eg. Select_priv and select_priv, they were considered as 
221
           definitions of 2 different access-rights.
222
    *      the IP-number of a host with a name containing wildcards should
223
           not be searched for in Name2IP and IP2Name.
224
    *      various other small things, pointed out by <monty> and <psmith>
225
226
  1.2         : (1997-05-13)
227
  - bugfix:
228
    * Fixed bug in acl with anonymous user:  Now if one gets accepted by the
229
      user table as a empty user name, the user name is set to '' when 
230
      checking against the 'db' and 'host' tables. (Bug fixed in MySQL3.20.19)
231
232
  1.2-1       : (1997-xx-xx)
233
  - bugfix:
234
    * hashes should  be initialized with () instead of {} <psmith>
235
    * "my" variable $name masks earlier declaration in same scope,
236
      using perl 5.004 <????>
237
238
  1.2-2       : (1997-06-10)
239
    
240
  2.0p1-3     : (1997-10-xx)
241
  - new
242
    * packages
243
    * log-file for debug-output : /tmp/mysqlaccess.log
244
    * default values are read from a configuration file $script.conf
245
      first this file is looked for in the current directory; if not
246
      found it is looked for in /etc/
247
      Note that when default-values are given, these can't get overriden
248
      by empty (blanc) values!
249
    * CGI-BIN version with HTML and forms interface.  Simply place the
250
      script in an ScriptAliased directory, make the configuration file
251
      available in the that directory or in /etc, and point your browser
252
      to the right URL. 
253
    * copy the grant-rules to temporary tables, where you are safe to
254
      play with them.
255
    * preview changes in privileges after changing grant-rules,
256
      before taking them into production
257
    * copy the new grant-rules from the temporary tables back to the
258
      grant-tables.
259
    * Undo all changes made in the grant-tables (1-level undo).
260
  -new options:
261
    * --table   : as opposite of the --brief option.
262
    * --copy    : (re)load temporary grant-tables from original ones.
263
    * --preview : preview changes in privileges after changing
264
                  some or more entries in the grant-tables.
265
    * --commit  : copy grant-rules from temporary tables to grant-tables
266
                  (!don't forget to do an mysqladmin reload)
267
    * --rollback: undo the last changes to the grant-tables.
268
269
  - bugfix:
270
    * if the table db is empty, mysqlaccess freezed 
271
      (by X Zhu <X.Zhu@Bradford.ac.uk>)
272
273
  2.0         : (1997-10-09)
274
  - fixed some "-w" warnings.
275
  - complain when certain programs and paths can't be found.
276
277
  2.01        : (1997-12-12)
278
  - bugfix:
279
    * rules for db-table where not calculated and reported correctly.
280
  2.02        : (1998-01-xx)
281
  - bugfix:
282
    * Privileges of the user-table were not AND-ed properly with the
283
      other privileges. (reported by monty)
284
  - new option:
285
    * --old_server: mysqlaccess will now use a full where clause when
286
                    retrieving information from the MySQL-server.  If
287
                    you are connecting to an old server (before v3.21)
288
                    then use the option --old_server.
289
  2.03         : (1998-02-27)
290
  - bugfix:
291
    * in Host::MatchTemplate: incorrect match if host-field was left empty.
292
293
  2.04-alpha1  : (2000-02-11)
294
  Closes vulnerability due to former implementation requiring passwords
295
  to be passed on the command line.
296
  - functionality
297
    Option values for --password -p -spassword -P  may now be omitted from
298
    command line, in which case the values will be prompted for.
299
      (fix supplied by Steve Harvey <sgh@vex.net>)
300
301
   2.05: (2000-02-17)   Monty
302
   Moved the log file from /tmp to ~
303
304
   2.06:  Don't print '+++USING FULL WHERE CLAUSE+++'
305
306
_RELEASE
307
308
$TODO = <<_TODO;
309
310
 Plans:
311
 -----
312
  -a full where clause is use now.  How can we handle older servers?
313
  -add some more functionality for DNS.
314
  -select the warnings more carefuly.
315
  >>  I think that the warnings should either be enhanced to _really_
316
  >>  understand and report real problems accurately, or restricted to
317
  >>  only printing things that it knows with 100% certainty. <psmith)
318
  >>  Why do I have both '%' and 'any_other_host' in there?  Isn't that
319
  >>  the same thing?  I think it's because I have an actual host '%' in
320
  >>  one of my tables.  Probably the script should catch that and not
321
  >>  duplicate output. <psmith>
322
323
_TODO
324
325
# From the FAQ: the Grant-algorithm
326
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
327
# The host table is mainly to maintain a list of "secure" servers.
328
# At TCX hosts contain a list of all machines on local network. These are granted
329
# all privileges.
330
# Technically the user grant is calculated by:
331
#
332
#    1.First sort all entries by host by putting host without wildcards first,
333
#      after this host with wildcards and entries with host = ".
334
#      Under each host sort user by the same criterias.
335
#    2.Get grant for user from the "db" table.
336
#    3.If hostname is "empty" for the found entry, AND the privileges with
337
#      the privileges for the host in "host" table.
338
#      (Remove all which is not "Y" in both)
339
#    4.OR (add) the privileges for the user from the "user" table.
340
#     (add all privileges which is "Y" in "user")
341
#
342
#    When matching, use the first found match.
343
#
344
# -----------------------------------------------------------------------------------
345
346
$HOWTO = <<_HOWTO;
347
348
Examples of how to call $script:
349
~~~~~~~~
350
1)Calling $script with 2 arguments:
351
352
  \$ $script root mysql
353
     ->report rights of user root logged on at the local host in db mysql
354
355
  Access-rights
356
  for USER 'root', from HOST 'localhost', to DB 'mysql'
357
          +-----------------+---+ +-----------------+---+
358
          | select_priv     | Y | | drop_priv       | Y |
359
          | insert_priv     | Y | | reload_priv     | Y |
360
          | update_priv     | Y | | shutdown_priv   | Y |
361
          | delete_priv     | Y | | process_priv    | Y |
362
          | create_priv     | Y | | file_priv       | Y |
363
          +-----------------+---+ +-----------------+---+
364
  BEWARE:  Everybody can access your DB as user 'root'
365
        :  WITHOUT supplying a password.  Be very careful about it!!
366
367
  The following rules are used:
368
   db    : 'No matching rule'
369
   host  : 'Not processed: host-field is not empty in db-table.'
370
   user  : 'localhost','root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y'
371
372
2)Calling $script with 3 arguments:
373
374
  \$ $script foo.bar nobody Foo 
375
     ->report rights of user root logged in at machine foobar to db Foo
376
377
  Access-rights
378
  for USER 'nobody', from HOST 'foo.bar', to DB 'Foo'
379
          +-----------------+---+ +-----------------+---+
380
          | select_priv     | Y | | drop_priv       | N |
381
          | insert_priv     | Y | | reload_priv     | N |
382
          | update_priv     | Y | | shutdown_priv   | N |
383
          | delete_priv     | Y | | process_priv    | N |
384
          | create_priv     | N | | file_priv       | N |
385
          +-----------------+---+ +-----------------+---+
386
  BEWARE:  Everybody can access your DB as user 'nobody'
387
        :  WITHOUT supplying a password.  Be very careful about it!!
388
389
  The following rules are used:
390
   db    : 'foo.bar','Foo','nobody','Y','Y','Y','N','N','N'
391
   host  : 'Not processed: host-field is not empty in db-table.'
392
   user  : 'foo.bar','nobody','','N','N','N','Y','N','N','N','N','N','N'
393
394
3)Using wildcards:
395
396
  \$ $script  \\* nobody Foo --brief
397
     ->report access-rights of user nobody from all machines to db Foo,
398
       and use a matrix-report.
399
400
  Sel  Ins  Upd  Del  Crea Drop Reld Shut Proc File Host,User,DB        
401
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --------------------
402
   Y    Y    Y    Y    N    N    N    N    N    N   localhost,nobody,Foo
403
   N    N    N    N    N    N    N    N    N    N   %,nobody,Foo  
404
   N    N    N    N    N    N    N    N    N    N   any_other_host,nobody,Foo
405
406
_HOWTO
407
408
409
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
410
#                       START OF THE PROGRAM                            #
411
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
412
413
use Getopt::Long;
414
use Sys::Hostname;
415
use IPC::Open3;
416
417
418
# ****************************
419
# debugging flag
420
# can be set to 0,1,2,3
421
# a higher value gives more info
422
# ! this can also be set on the command-line
423
	$DEBUG   = 0;
424
425
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>8
426
#  Normaly nothing should be changed beneeth this line
427
428
429
# ****************************
430
# no caching on STDOUT
431
	$|=1;
432
433
	$MYSQL_CNF = tmpnam();
434
	%MYSQL_CNF = (client    => { },
435
                      mysql     => { },
436
                      mysqldump => { },
437
	);
438
439
440
441
$NEW_USER = 'ANY_NEW_USER';
442
$NEW_DB   = 'ANY_NEW_DB'  ;
443
444
445
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #
446
#  mysqlaccess:                                              #
447
#  ~~~~~~~~~~~                                               #
448
#  Lets get to it,                                           #
449
#  and start the program by processing the parameters        #
450
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #
451
452
($CMD,$CGI) = GetMode();
453
454
# ****************************
455
# the copyright message should
456
# always be printed (once)
457
MySQLaccess::Report::Print_Header();
458
459
# *****************************
460
# Read configuration-file
461
  MySQLaccess::Debug::Print(1, "Reading configuration file...");
462
  if (-f "./$script_conf") {
463
     require "./$script_conf";
464
  }
465
  elsif (-f "@sysconfdir@/$script_conf") {
466
     require "@sysconfdir@/$script_conf";
467
  }
468
  elsif (-f "/etc/$script_conf") {
469
     require "/etc/$script_conf";
470
  }
471
472
# ****************************
473
# Read in all parameters
474
if ($MySQLaccess::CMD) { #command-line version
475
	# ----------------------------
476
	# Get options from commandline
477
	$Getopt::Long::ignorecase=0; #case sensitive options
478
	if ( grep(/\-\?/,@ARGV) ) { MySQLaccess::Report::Print_Usage(); exit 0; }
479
	GetOptions("help"          => \$Param{'help'}
480
	          ,"host|h=s"      => \$Param{'host'}
481
	          ,"user|u=s"      => \$Param{'user'}
482
	          ,"password|p:s"  => \$Param{'password'}
483
	          ,"db|d=s"        => \$Param{'db'}
484
	          ,"superuser|U=s" => \$Param{'superuser'}
485
	          ,"spassword|P:s" => \$Param{'spassword'}
486
	          ,"rhost|H=s"     => \$Param{'rhost'}
487
                  ,"old_server"    => \$Param{'old_server'}
488
	          ,"debug=i"       => \$Param{'DEBUG'}
489
	          ,"brief|b"       => \$Param{'brief'}
490
	          ,"table|t"       => \$Param{'table'}
491
	          ,"relnotes"      => \$Param{'relnotes'}
492
	          ,"plan"          => \$Param{'plan'}
493
	          ,"howto"         => \$Param{'howto'}
494
	          ,"version|v"     => \$Param{'version'}
495
                  ,"preview"       => \$Param{'preview'}
496
                  ,"copy"          => \$Param{'copy'}
497
                  ,"commit"        => \$Param{'commit'}
498
                  ,'rollback'      => \$Param{'rollback'}
499
		  );
500
501
        # -----------------------------
502
        # set DEBUG
503
        $DEBUG = $Param{'DEBUG'} if ($Param{'DEBUG'}>=$DEBUG);
504
505
	# -----------------------------
506
	# check for things which aren't
507
	# declared as options:
508
	# 2 arguments: (user,db) -> ('localhost','user','db')
509
	if ($#ARGV == 1) {
510
	   MySQLaccess::Debug::Print(2,"$script called with 2 arguments:");
511
	   $Param{'host'} = $Param{'host'} || 'localhost'; 
512
	   $Param{'user'} = $ARGV[0] || $Param{'user'};
513
           $Param{'db'}   = $ARGV[1] || $Param{'db'}; 
514
	}
515
	# 3 arguments: (host,user,db)
516
	if ($#ARGV == 2) {
517
	   MySQLaccess::Debug::Print(2,"$script called with 3 arguments:");
518
	   $Param{'host'} = $ARGV[0] || $Param{'host'};
519
	   $Param{'user'} = $ARGV[1] || $Param{'user'};
520
	   $Param{'db'}   = $ARGV[2] || $Param{'db'};
521
	}
522
523
	# -------------------------------------
524
	# prompt for user password if requested
525
	if ( defined($Param{'password'}) && length($Param{'password'}) == 0 ) {
526
           $Param{'password'} = PromptPass(
527
	                        "Password for MySQL user $Param{'user'}: ");
528
	}
529
}
530
if ($MySQLaccess::CGI) { #CGI-version
531
	require CGI;
532
 	$Q = new CGI;
533
	$Param{'help'} = $Q->param('help') ;
534
	$Param{'host'} = $Q->param('host') || $Q->param('h') || $Param{'host'};
535
	$Param{'user'} = $Q->param('user') || $Q->param('u') || $Param{'user'};
536
	$Param{'db'}   = $Q->param('db')   || $Q->param('d') || $Param{'db'};
537
	$Param{'password'}  = $Q->param('password')  || $Q->param('p') || $Param{'password'};
538
	$Param{'superuser'} = $Q->param('superuser') || $Q->param('U') || $Param{'superuser'};
539
	$Param{'spassword'} = $Q->param('spassword') || $Q->param('P') || $Param{'spassword'};
540
	$Param{'rhost'}     = $Q->param('rhost')     || $Q->param('H') || $Param{'rhost'};
541
	$Param{'old_server'}= $Q->param('old_server')|| $Param{'old_server'};
542
	$Param{'debug'}     = $Q->param('debug')     || $Param{'debug'};
543
	$Param{'brief'}     = $Q->param('brief')     || $Param{'brief'}; 
544
	$Param{'table'}     = $Q->param('table')     || $Param{'table'}; 
545
	$Param{'relnotes'}  = $Q->param('relnotes');
546
	$Param{'plan'}      = $Q->param('plan');
547
	$Param{'howto'}     = $Q->param('howto'); 
548
	$Param{'version'}   = $Q->param('version') ? $Q->param('version') : $Q->param('v');
549
	$Param{'edit'}      = $Q->param('edit'); 
550
	$Param{'preview'}   = $Q->param('preview'); 
551
	$Param{'copy'}      = $Q->param('copy'); 
552
	$Param{'commit'}    = $Q->param('commit'); 
553
	$Param{'rollback'}  = $Q->param('rollback'); 
554
        # -----------------------------
555
        # set DEBUG
556
        $DEBUG = $Q->param('debug') if ($Q->param('debug')>=$DEBUG);
557
}
558
559
# ----------------------
560
# brief and table-format 
561
# exclude each-other
562
# table-format is prefered
563
if (defined($Param{'table'})) { undef($Param{'brief'}); }
564
if (defined($Param{'preview'}) or
565
    defined($Param{'copy'}) or
566
    defined($Param{'commit'}) or
567
    defined($Param{'rollback'}) ) { $Param{'edit'}='on'; }
568
569
570
# ----------------------
571
# if no host is given
572
# assume we mean 'localhost'
573
if (!defined($Param{'host'}))      { $Param{'host'}='localhost'; }
574
575
# ----------------------
576
# perform some checks
577
# -> eliminate 'broken pipe' error
578
push(@MySQLaccess::Grant::Error,'not_found_mysql')     if !(-x $MYSQL);
579
push(@MySQLaccess::Grant::Error,'not_found_diff')      if !(-x $DIFF);
580
push(@MySQLaccess::Grant::Error,'not_found_mysqldump') if !(-x $MYSQLDUMP);
581
if (@MySQLaccess::Grant::Error) {
582
   MySQLaccess::Report::Print_Error_Messages() ;
583
   exit 0;
584
}
585
586
#-----------------------
587
# get info/help if necc.
588
$print_usage=1;
589
if ( defined($Param{'version'}) ) {
590
   MySQLaccess::Report::Print_Version();
591
   $print_usage=0;
592
   MySQLaccess::Report::Print_Footer();
593
   MySQLaccess::DB::CloseConnection();
594
   exit 0;
595
#   exit 0;
596
}
597
if ( defined($Param{'relnotes'}) ) {
598
   MySQLaccess::Report::Print_Relnotes();
599
   $print_usage=0;
600
   MySQLaccess::Report::Print_Footer();
601
   MySQLaccess::DB::CloseConnection();
602
   exit 0;
603
#   exit 0;
604
}
605
if ( defined($Param{'plan'}) ) {
606
   MySQLaccess::Report::Print_Plans();
607
   $print_usage=0;
608
   MySQLaccess::Report::Print_Footer();
609
   MySQLaccess::DB::CloseConnection();
610
   exit 0;
611
#   exit 0;
612
}
613
if ( defined($Param{'howto'}) ) {
614
   MySQLaccess::Report::Print_HowTo();
615
   $print_usage=0;
616
   MySQLaccess::Report::Print_Footer();
617
   MySQLaccess::DB::CloseConnection();
618
   exit 0;
619
#   exit 0;
620
}
621
622
# -----------------------------
623
# generate a help-screen in CMD-mode
624
# or a blanc form in CGI-mode 
625
if ( defined($Param{'help'}) 
626
     or !defined($Param{'user'}) 
627
     or !defined($Param{'host'})
628
     or !defined($Param{'db'}) 
629
   ) {
630
   push(@MySQLaccess::Grant::Error,'user_required') unless defined($Param{'user'});
631
   push(@MySQLaccess::Grant::Error,'db_required') unless defined($Param{'db'});
632
   push(@MySQLaccess::Grant::Error,'host_required') unless defined($Param{'host'});
633
   MySQLaccess::Report::Print_Usage() if $print_usage;
634
   exit 0;
635
}
636
637
638
# ----------------------------
639
# get hostname and local-ip
640
# for localhost
641
$localhost = MySQLaccess::Host::LocalHost();
642
$local_ip  = MySQLaccess::Host::Name2IP($localhost);
643
$MySQLaccess::Host::localhost = MySQLaccess::Host::LocalHost();
644
$MySQLaccess::Host::local_ip  = MySQLaccess::Host::Name2IP($localhost);
645
MySQLaccess::Debug::Print(3, "localhost name=$localhost, ip=$local_ip");
646
647
#-----------------------------------
648
# version of MySQL-server to connect
649
# to determine use of full where clause
650
$MySQLaccess::Host::SERVER = $Param{'old_server'} ? '3.20' : $SERVER;
651
652
#---------------------------------
653
# create the config file for mysql and mysqldump
654
# to avoid passing authentication info on the command line
655
#
656
MergeConfigFiles();
657
die "Unsafe config file found: $unsafeConfig\n"  if $unsafeConfig;
658
if (defined($Param{'superuser'})) {
659
   $MYSQL_CNF{'mysql'}{'user'} = $Param{'superuser'};
660
   $MYSQL_CNF{'mysqldump'}{'user'} = $Param{'superuser'};
661
}
662
if (defined($Param{'spassword'})) {
663
   if ( $CMD && length($Param{'spassword'}) == 0 ) {
664
      $Param{'spassword'} =
665
           PromptPass("Password for MySQL superuser $Param{'superuser'}: ");
666
   }
667
   if ( length($Param{'spassword'}) > 0 ) {
668
      $MYSQL_CNF{'mysql'}{'password'} = $Param{'spassword'};
669
      $MYSQL_CNF{'mysqldump'}{'password'} = $Param{'spassword'};
670
   }
671
}
672
WriteTempConfigFile();
673
674
#---------------------------------
675
# Inform user if he has not enough
676
# privileges to read the access-db
677
if ( $nerror=MySQLaccess::DB::OpenConnection() ) {
678
    MySQLaccess::Report::Print_Error_Access($nerror);
679
    exit 0;
680
}
681
682
# -----------------------
683
# Read MySQL ACL-files
684
if ($nerror=MySQLaccess::Grant::ReadTables()) {
685
    MySQLaccess::Report::Print_Error_Access($nerror);
686
    exit 0;
687
};
688
if ($Param{'edit'} and $nerror=MySQLaccess::Grant::ReadTables('tmp')) {
689
    MySQLaccess::Report::Print_Error_Access($nerror);
690
    exit 0;
691
}
692
693
#---------------------------------
694
# reload temporay grant-tables 
695
# with data from original ones
696
if ( defined($Param{'copy'}) ) {
697
   $nerror=MySQLaccess::DB::LoadTmpTables();
698
   if ($nerror) {
699
      MySQLaccess::Report::Print_Error_Access($nerror);
700
      exit 0;
701
   }
702
   my $msg = "The grant-rules are copied from the grant-tables to\n"
703
           . "the temporary tables.";
704
   MySQLaccess::Report::Print_Message([$msg]);
705
#   MySQLaccess::Report::Print_Footer();
706
#   MySQLaccess::DB::CloseConnection();
707
#   exit 0;
708
}
709
710
711
#---------------------------------
712
# preview result of changes in the 
713
# grant-tables
714
if ( defined($Param{'preview'}) ) {
715
   $aref=MySQLaccess::Grant::Diff_Privileges();
716
   MySQLaccess::Report::Print_Diff_ACL($aref);
717
#   MySQLaccess::Report::Print_Footer();
718
#   MySQLaccess::DB::CloseConnection();
719
#   exit 0;
720
}
721
722
723
#---------------------------------
724
# reload grant-tables 
725
# with data from temporary tables
726
if ( defined($Param{'commit'}) ) {
727
   if ($nerror = MySQLaccess::DB::CommitGrantTables()) {
728
      MySQLaccess::Report::Print_Error_Access($nerror);
729
      exit 0;
730
   }
731
   my $msg = "The grant-rules have been copied from the temporary tables\n"
732
           . "to the grant-tables.";
733
   my $msg1= "Don't forget to do an 'mysqladmin reload' before these\n"
734
           . "changes take effect.";
735
   my $msg2= "A backup-version of your original grant-rules are saved in the\n"
736
           . "backup-tables, so you can always perform a 1-level rollback.";
737
   MySQLaccess::Report::Print_Message([$msg,$msg1,$msg2]);
738
#   MySQLaccess::Report::Print_Footer();
739
#   MySQLaccess::DB::CloseConnection();
740
#   exit 0;
741
}
742
743
#---------------------------------
744
# restore previous grant-rules
745
# with data from backup tables
746
if ( defined($Param{'rollback'}) ) {
747
   if ($nerror = MySQLaccess::DB::RollbackGrantTables()) {
748
      MySQLaccess::Report::Print_Error_Access($nerror);
749
      exit 0;
750
   }
751
   my $msg = "The old grant-rules have been copied back from the backup tables\n"
752
           . "to the grant-tables.";
753
   my $msg1= "Don't forget to do an 'mysqladmin reload' before these\n"
754
           . "changes take effect.";
755
   MySQLaccess::Report::Print_Message([$msg,$msg1]);
756
#   MySQLaccess::Report::Print_Footer();
757
#   MySQLaccess::DB::CloseConnection();
758
#   exit 0;
759
}
760
#----------------------------------
761
# show edit-taskbar
762
if ( defined($Param{'edit'})) {
763
   if ($MySQLaccess::CGI ) {
764
   MySQLaccess::Report::Print_Edit();
765
   $print_usage=0;
766
   MySQLaccess::Report::Print_Footer();
767
   MySQLaccess::DB::CloseConnection();
768
   exit 0;
769
   }
770
   else {
771
   MySQLaccess::Report::Print_Edit();
772
   $print_usage=0;
773
   MySQLaccess::Report::Print_Footer();
774
   MySQLaccess::DB::CloseConnection();
775
   exit 0;
776
   }
777
}
778
779
780
# -----------------------------
781
# Build list of users,dbs,hosts
782
# to process...
783
@all_dbs   = @{MySQLaccess::DB::Get_All_dbs($Param{'db'})};
784
@all_users = @{MySQLaccess::DB::Get_All_users($Param{'user'})};
785
@all_hosts = @{MySQLaccess::DB::Get_All_hosts($Param{'host'})};
786
#if EDIT-mode
787
#@all_dbs_tmp   = @{MySQLaccess::DB::Get_All_dbs($Param{'db'},'tmp')};
788
#@all_users_tmp = @{MySQLaccess::DB::Get_All_users($Param{'user'},'tmp')};
789
#@all_hosts_tmp = @{MySQLaccess::DB::Get_All_hosts($Param{'host'},'tmp')};
790
791
# -----------------------------
792
# Report access-rights for each
793
# tuple (host,user,db)
794
#$headers=0;
795
my %Access = ();
796
foreach $host (@all_hosts) {
797
  foreach $user (@all_users) {
798
    foreach $db (@all_dbs) {
799
      MySQLaccess::Grant::Initialize();
800
      %Access = MySQLaccess::Grant::Get_Access_Rights($host,$user,$db);	
801
      MySQLaccess::Report::Print_Access_rights($host,$user,$db,\%Access);
802
    }
803
  }
804
}
805
806
# -----------------------------
807
# End script
808
MySQLaccess::Report::Print_Footer();
809
MySQLaccess::DB::CloseConnection();
810
exit 0;
811
812
#############################################################
813
#  FUNCTIONS  #
814
###############
815
sub GetMode {
816
   my $cmd=0;
817
   my $cgi=0;
818
   if (defined($ENV{'HTTP_HOST'})) { $cmd=0; $cgi=1; }
819
   else                            { $cmd=1; $cgi=0; } 
820
   return ($cmd,$cgi);
821
}
822
823
# ================================
824
# sub PromptPass
825
#  prompt tty for a password
826
# ================================
827
sub PromptPass {
828
    my ($prompt) = @_;
829
    my $password;
830
    $ENV{PATH} = "/bin:/usr/bin";
831
    $ENV{IFS} = " \t\n";
832
    $ENV{SHELL} = "/bin/sh";
833
    system "stty -echo";
834
    print $prompt;
835
    chomp($password = <STDIN>);
836
    print "\n";
837
    system "stty echo";
838
    $password;
839
}
840
841
# =================================
842
# sub CheckUnsafeFile
843
#  tell if a config file containing a password is unsafe
844
# =================================
845
sub CheckUnsafeFile {
846
    my ($fname) = @_;
847
    my ($dev, $ino, $mode, $nlink,
848
        $uid, $gid, $rdev, $size,
849
        $atime, $mtime, $ctime, $blksize, $blocks) = stat($fname);
850
851
    if ( $uid != $< ) {   # unsafe if owned by other than current user
852
        return 1;
853
    }
854
    if ( $mode & 066 ) {  # unsafe if accessible by other
855
        return 1;
856
    }
857
    $fname =~ s#/[^/]+$##;
858
    if ( (length $fname) > 0 ) {
859
        return CheckUnsafeDir($fname);
860
    }
861
    return 0;
862
}
863
864
# =================================
865
# sub CheckUnsafeDir
866
#  tell if a directory is unsafe
867
# =================================
868
sub CheckUnsafeDir {
869
    my ($fname) = @_;
870
    my ($dev, $ino, $mode, $nlink,
871
        $uid, $gid, $rdev, $size,
872
        $atime, $mtime, $ctime, $blksize, $blocks) = stat($fname);
873
874
    # not owned by me or root
875
    if ( ($uid != $<) && ($uid != 0) ) {
876
        return 1;
877
    }
878
    if ( $mode & 022 ) {  # unsafe if writable by other
879
        return 1  unless $mode & 01000;  # but sticky bit ok
880
    }
881
    $fname =~ s#/[^/]+$##;
882
    if ( (length $fname) > 0 ) {
883
        return CheckUnsafeDir($fname);
884
    }
885
    return 0;
886
}
887
888
# =================================
889
# sub MergeConfigFile
890
#  merge data from .cnf file
891
# =================================
892
sub MergeConfigFile {
893
    my ($fname) = @_;
894
    my ($group, $item, $value);
895
    if ( open CNF, $fname ) {
896
         while (<CNF>) {
897
             s/^\s+//;
898
             next if /^[#;]/;
899
             if ( /\[\s*(\w+)\s*]/ ) {
900
                 $group = $1;
901
                 $group =~ tr/A-Z/a-z/;
902
                 if ( !exists $MYSQL_CNF{$group} ) {
903
                     undef $group;
904
                 }
905
             } elsif ( defined $group ) {
906
                 ($item, $value) = /((?:\w|-)+)\s*=\s*(\S+)/;
907
                 # don't unquote backslashes as we just write it back out
908
                 if ( defined $item ) {
909
                     if ( $item =~ /^password$/ ) {
910
                         if ( CheckUnsafeFile($fname) ) {
911
                             $unsafeConfig = $fname;
912
                         }
913
                     }
914
                     if ( $group eq 'client' ) {
915
                         $MYSQL_CNF{'mysql'}{$item} = $value;
916
                         $MYSQL_CNF{'mysqldump'}{$item} = $value;
917
                     } else {
918
                         $MYSQL_CNF{$group}{$item} = $value;
919
                     }
920
                 }
921
             }
922
         }
923
         close(CNF);
924
    }
925
}
926
927
# =================================
928
# sub MergeConfigFiles
929
#  merge options from config files
930
#  NOTE: really should do two separate merges for each
931
#    client to exactly duplicate order of resulting argument lists
932
# =================================
933
sub MergeConfigFiles {
934
    my ($name,$pass,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwuid $<;
935
    MergeConfigFile("@sysconfdir@/my.cnf");
936
    MergeConfigFile("/etc/my.cnf");
937
    MergeConfigFile("$dir/.my.cnf");
938
}
939
940
# =================================
941
# sub WriteTempConfigFile
942
#  write 
943
# =================================
944
sub WriteTempConfigFile {
945
   sysopen CNFFILE, $MYSQL_CNF, O_RDWR|O_CREAT|O_EXCL, 0700
946
      or die "sysopen $MYSQL_CNF: $!";
947
   
948
   # groups may be in any order, generic groups such as [client] assumed
949
   # here to be empty
950
   foreach $group (keys %MYSQL_CNF) {
951
      print CNFFILE "[$group]\n";
952
      foreach $item (keys %{$MYSQL_CNF{$group}}) {
953
         if ( defined $MYSQL_CNF{$group}{$item} ) {
954
            print CNFFILE "$item=$MYSQL_CNF{$group}{$item}\n";
955
         } else {
956
            print CNFFILE "$item\n";
957
         }
958
      }
959
      print CNFFILE "\n";
960
   }
961
   close(CNFFILE);
962
}
963
964
######################################################################
965
package MySQLaccess::DB;
966
###########
967
BEGIN {
968
    $DEBUG     = 2;
969
    $DEBUG     = $MySQLaccess::DEBUG unless ($DEBUG);
970
    # Error-messages from the MySQL client
971
    %ACCESS_ERR= ('Access_denied'       => 'Access denied' 
972
                 ,'Dbaccess_denied'     => 'Access to database denied'
973
                 ,'Unrecognized_option' => 'unrecognized option' 
974
                 ,'Unknown_table'       => "Can't find file:"
975
                 ,'unknown_error'       => '^ERROR:'
976
                 );
977
}
978
# ######################################
979
#  Connecting to the MYSQL DB
980
# ======================================
981
# sub OpenConnection
982
#  Open an connection to the mysql-db
983
#  questions to MYSQL_Q
984
#  answers from MYSQL_A
985
# ======================================
986
sub OpenConnection {
987
    my $pid;
988
    MySQLaccess::Debug::Print(2,"OpenConnection:");
989
990
    # check path to mysql-client executable
991
    if (! -f $MySQLaccess::MYSQL) {
992
       if ($MySQLaccess::CMD) { die "Could not find MySQL-client '$MySQLaccess::MYSQL'"; }
993
       if ($MySQLaccess::CGI) { 
994
          print "<center>\n<font color=Red>\n";
995
          print "ERROR: Could not find MySQL-client '$MySQLaccess::MYSQL'";
996
          print "</center>\n</font>\n";
997
          exit 0;
998
       }
999
    }
1000
1001
    # path to mysql executable
1002
    my $connect = "$MySQLaccess::MYSQL --defaults-file=$MySQLaccess::MYSQL_CNF";
1003
    $connect .= " $MySQLaccess::MYSQL_OPT";
1004
    # superuser, spassword transmitted via defaults-file
1005
    if (defined($MySQLaccess::Param{'rhost'}))     { $connect .= " --host=$MySQLaccess::Param{'rhost'}"; }
1006
    # other options??
1007
1008
    # grant-database
1009
    $connect .= " $MySQLaccess::ACCESS_DB";
1010
1011
    # open connection (not using /bin/sh -c)
1012
    MySQLaccess::Debug::Print(2,"Connecting to: $connect");
1013
    $pid=IPC::Open3::open3(\*MYSQL_Q,\*MYSQL_A,"",split /\s+/,$connect);
1014
    MySQLaccess::Debug::Print(2,"PID of open pipe: $pid");
1015
    
1016
    # check connection 
1017
    print MYSQL_Q "select 'ok';\n";
1018
    $answer = <MYSQL_A>; #answer from mysql
1019
    MySQLaccess::Debug::Print(2,"Answer: $answer\n");
1020
    foreach $nerror (sort(keys(%ACCESS_ERR))) {
1021
      MySQLaccess::Debug::Print(3,"check answer for error $ACCESS_ERR{$nerror}");
1022
      if (grep(/$ACCESS_ERR{$nerror}/i,$answer)) { 
1023
         MySQLaccess::Debug::Print(2,"Answer contain error [$nerror]");
1024
         return $nerror; 
1025
      }
1026
    }
1027
1028
if (0) {
1029
    # check server-version 
1030
    print MYSQL_Q "select 'ok';\n";
1031
    $answer = <MYSQL_A>; #answer from mysql
1032
    MySQLaccess::Debug::Print(2,"Answer: $answer\n");
1033
    foreach $nerror (sort(keys(%ACCESS_ERR))) {
1034
      MySQLaccess::Debug::Print(3,"check answer for error $ACCESS_ERR{$nerror}");
1035
      if (grep(/$ACCESS_ERR{$nerror}/i,$answer)) { 
1036
         MySQLaccess::Debug::Print(2,"Answer contain error [$nerror]");
1037
         return $nerror; 
1038
      }
1039
    }
1040
}
1041
1042
    my $skip=<MYSQL_A>; 
1043
    return 0; 
1044
}
1045
1046
# ======================================
1047
# sub CloseConnection
1048
#  Close the connection to the mysql-db
1049
# ======================================
1050
sub CloseConnection {
1051
    close MYSQL_Q;
1052
    close MYSQL_A;
1053
}
1054
1055
# ===========================================================
1056
# sub CreateTable($table)
1057
#  Create temporary/backup table
1058
# ===========================================================
1059
sub CreateTable {
1060
    my $pid;
1061
    my ($table,$force) = @_;
1062
    my %tables = ( $MySQLaccess::ACCESS_U_TMP => $MySQLaccess::ACCESS_U,
1063
                   $MySQLaccess::ACCESS_H_TMP => $MySQLaccess::ACCESS_H,
1064
                   $MySQLaccess::ACCESS_D_TMP => $MySQLaccess::ACCESS_D,
1065
                   $MySQLaccess::ACCESS_U_BCK => $MySQLaccess::ACCESS_U,
1066
                   $MySQLaccess::ACCESS_H_BCK => $MySQLaccess::ACCESS_H,
1067
                   $MySQLaccess::ACCESS_D_BCK => $MySQLaccess::ACCESS_D,
1068
                   $MySQLaccess::ACCESS_U => $MySQLaccess::ACCESS_U_BCK,
1069
                   $MySQLaccess::ACCESS_H => $MySQLaccess::ACCESS_H_BCK,
1070
                   $MySQLaccess::ACCESS_D => $MySQLaccess::ACCESS_D_BCK,
1071
                 ); 
1072
    my $tbl;
1073
    my $query="";
1074
    my $delim;
1075
    my $skip;
1076
    my $create;
1077
    my @known_tables=();
1078
1079
#    print STDERR "CreateTable($table)\n";
1080
    MySQLaccess::Debug::Print(1,"CreateTable($table):");
1081
1082
    ## error-handling
1083
    return 'Unknown_table' unless defined($tables{$table});
1084
1085
    ## build list of known/existing tables;
1086
    ## if 'force' existing table is dropped first
1087
    if (defined($force) and $force) {
1088
       @known_tables = Show_Tables();
1089
       if (grep(/^$table$/,@known_tables)) {
1090
       $query = "DROP TABLE $table;";
1091
       }
1092
    }
1093
1094
    ## path to mysqldump executable
1095
    my $connect = $MySQLaccess::MYSQLDUMP;
1096
    $connect .= " --defaults-file=$MySQLaccess::MYSQL_CNF --no-data";
1097
    # superuser, spassword transmitted via defaults-file
1098
    if (defined($MySQLaccess::Param{'rhost'}))     { $connect .= " --host=$MySQLaccess::Param{'rhost'}"; }
1099
    $connect .= " $MySQLaccess::ACCESS_DB";
1100
    $connect .= " $tables{$table}";
1101
1102
1103
    ## get creation-data for original table
1104
    $create = '';
1105
    my $mysqldump = $connect;
1106
    $mysqldump =~ s/ \$TABLE / $tbl /;
1107
1108
    # open connection (not using /bin/sh -c)
1109
    MySQLaccess::Debug::Print(2,"Connecting to: $connect");
1110
    $pid=IPC::Open3::open3(\*DONTCARE,\*CREATE,"",split /\s+/,$mysqldump);
1111
    MySQLaccess::Debug::Print(2,"PID of open pipe: $pid");
1112
    #open(CREATE,"$mysqldump");
1113
    @create = <CREATE>;
1114
    $create = "@create";
1115
    foreach $nerror (sort(keys(%ACCESS_ERR))) {
1116
       MySQLaccess::Debug::Print(3,"check answer for error $ACCESS_ERR{$nerror}");
1117
       if (grep(/$ACCESS_ERR{$nerror}/i,$create)) { 
1118
          MySQLaccess::Debug::Print(2,"Answer contain error [$nerror]");
1119
          return $nerror; 
1120
       }
1121
    }
1122
    close(CREATE);
1123
    close(DONTCARE);
1124
1125
    ## manipulate result for creation-data for temporary table
1126
    $create =~ s/CREATE TABLE $tables{$table} \(/CREATE TABLE $table \(/;
1127
1128
    ## recreate temporary table
1129
    $query .= "$create\n";
1130
    $query .= "select 'ok';";
1131
1132
    ## execute query
1133
    print MYSQL_Q "$query\n";
1134
#    print STDERR $query;
1135
1136
    $answer = <MYSQL_A>; #answer from mysql
1137
#    print STDERR "A>",$answer;
1138
    MySQLaccess::Debug::Print(2,"Answer: $answer\n");
1139
    foreach $nerror (sort(keys(%ACCESS_ERR))) {
1140
#       print STDERR "->$nerror?";
1141
       MySQLaccess::Debug::Print(3,"check answer for error $ACCESS_ERR{$nerror}");
1142
       if (grep(/$ACCESS_ERR{$nerror}/i,$answer)) { 
1143
#          print STDERR "Yes!";
1144
          MySQLaccess::Debug::Print(2,"Answer contain error [$nerror]");
1145
          return $nerror; 
1146
       }
1147
    }
1148
1149
    $delim = <MYSQL_A>; # read header
1150
    if ($delim ne "ok\n") {
1151
       while (($line=<MYSQL_A>) ne "ok\n")
1152
       { MySQLaccess::Debug::Print(3," A> $line"); }
1153
        $skip = <MYSQL_A>; # skip result 'ok'
1154
    }
1155
#    print STDERR "CreateTable done\n";
1156
    return 0;
1157
}
1158
1159
1160
# ===========================================================
1161
# sub CopyTable()
1162
#  Copy the structure and the data of a table to another table
1163
# ===========================================================
1164
sub CopyTable {
1165
    my ($from,$to,$force) = @_;
1166
    my @known_tables  = Show_Tables();    
1167
    my $query = "";
1168
    my $nerror= 0;
1169
    my $skip;
1170
1171
#    print STDERR "CopyTable($from,$to)\n";
1172
    MySQLaccess::Debug::Print(1,"MySQLaccess::DB::CopyTable($from,$to)");
1173
1174
    ## error-handling
1175
    if (!grep(/^$from$/,@known_tables)) { return 'Unknown_table'; }
1176
1177
    ## copy structure 
1178
    ## if forced
1179
    if (defined($force) and $force) {
1180
       return $nerror if ($nerror=CreateTable($to,$force)); 
1181
#       print STDERR "Structure copied\n";
1182
    }
1183
1184
    ## copy data
1185
    $query .= "DELETE FROM $to;";
1186
    $query .= "INSERT INTO $to SELECT * FROM $from;";
1187
    $query .= "SELECT 'ok';\n";
1188
    MySQLaccess::Debug::Print(2,"Query: $query");
1189
       
1190
    ## execute query
1191
    print MYSQL_Q "$query\n";
1192
#    print STDERR $query;
1193
1194
    ## check for errors...
1195
    my $answer = <MYSQL_A>; #answer from mysql
1196
#    print STDERR $answer;
1197
    MySQLaccess::Debug::Print(2,"Answer: $answer\n");
1198
    foreach $nerror (sort(keys(%ACCESS_ERR))) {
1199
       MySQLaccess::Debug::Print(3,"check answer for error $ACCESS_ERR{$nerror}");
1200
       if (grep(/$ACCESS_ERR{$nerror}/i,$answer)) { 
1201
          MySQLaccess::Debug::Print(2,"Answer contain error [$nerror]");
1202
          return $nerror; 
1203
       }
1204
    }
1205
1206
    my $delim = <MYSQL_A>; # read header
1207
#    print STDERR $delim;
1208
    if ($delim ne "ok\n") {
1209
       while (($line=<MYSQL_A>) ne "ok\n")
1210
       { MySQLaccess::Debug::Print(3," A> $line"); }
1211
       $skip = <MYSQL_A>; # skip result 'ok'
1212
    }
1213
1214
    return 0;
1215
}
1216
1217
# ===========================================================
1218
# sub LoadTmpTables()
1219
#  (Re)load temporary tables with entries of ACL-tables
1220
# ===========================================================
1221
sub LoadTmpTables {
1222
    my %tables = ( $MySQLaccess::ACCESS_U => $MySQLaccess::ACCESS_U_TMP,
1223
                   $MySQLaccess::ACCESS_H => $MySQLaccess::ACCESS_H_TMP,
1224
                   $MySQLaccess::ACCESS_D => $MySQLaccess::ACCESS_D_TMP,
1225
                 ); 
1226
    my $tbl;
1227
    my $nerror;
1228
    
1229
#    print STDERR "LoadTmpTables:\n";
1230
    MySQLaccess::Debug::Print(1,"LoadTmpTables():");
1231
    foreach $tbl (keys(%tables)) {
1232
#       print STDERR "$tbl -> $tables{$tbl}\n";
1233
       MySQLaccess::Debug::Print(2,"Loading table $tbl -> $tables{$tbl}.");
1234
       return $nerror if ($nerror=CopyTable($tbl,$tables{$tbl},'force'));
1235
    }
1236
    return 0;
1237
}
1238
1239
# ===========================================================
1240
# sub BackupGrantTables()
1241
#  Make a backup of the original grant-tables
1242
# ===========================================================
1243
sub BackupGrantTables {
1244
    my %tables = ( $MySQLaccess::ACCESS_U => $MySQLaccess::ACCESS_U_BCK,
1245
                   $MySQLaccess::ACCESS_H => $MySQLaccess::ACCESS_H_BCK,
1246
                   $MySQLaccess::ACCESS_D => $MySQLaccess::ACCESS_D_BCK,
1247
                 ); 
1248
    my $tbl;
1249
    my $nerror;
1250
    
1251
#    print STDERR "BackupGrantTables:\n";
1252
    MySQLaccess::Debug::Print(1,"BackupGrantTables():");
1253
    foreach $tbl (keys(%tables)) {
1254
#       print STDERR "$tbl -> $tables{$tbl}\n";
1255
       MySQLaccess::Debug::Print(2,"Backup table $tbl -> $tables{$tbl}.");
1256
       return $nerror if ($nerror=CopyTable($tbl,$tables{$tbl},'force'));
1257
    }
1258
    return 0;
1259
}
1260
1261
# ===========================================================
1262
# sub RollbackGrantTables()
1263
#  Rollback the backup of the grant-tables
1264
# ===========================================================
1265
sub RollbackGrantTables {
1266
    my %tables = ( $MySQLaccess::ACCESS_U_BCK => $MySQLaccess::ACCESS_U,
1267
                   $MySQLaccess::ACCESS_H_BCK => $MySQLaccess::ACCESS_H,
1268
                   $MySQLaccess::ACCESS_D_BCK => $MySQLaccess::ACCESS_D,
1269
                 ); 
1270
    my $tbl;
1271
    my $nerror;
1272
    
1273
#    print STDERR "RollbackGrantTables:\n";
1274
    MySQLaccess::Debug::Print(1,"RollbackGrantTables():");
1275
    foreach $tbl (keys(%tables)) {
1276
#       print STDERR "$tbl -> $tables{$tbl}\n";
1277
       MySQLaccess::Debug::Print(2,"Rollback table $tbl -> $tables{$tbl}.");
1278
       return $nerror if ($nerror=CopyTable($tbl,$tables{$tbl},'force'));
1279
    }
1280
    return 0;
1281
}
1282
1283
1284
# ===========================================================
1285
# sub CommitGrantTables()
1286
#  Copy grant-rules from temporary tables to the ACL-tables
1287
# ===========================================================
1288
sub CommitGrantTables {
1289
    my %tables = ( $MySQLaccess::ACCESS_U => $MySQLaccess::ACCESS_U_TMP,
1290
                   $MySQLaccess::ACCESS_H => $MySQLaccess::ACCESS_H_TMP,
1291
                   $MySQLaccess::ACCESS_D => $MySQLaccess::ACCESS_D_TMP,
1292
                 ); 
1293
    my $tbl;
1294
    my $query;
1295
    my $delim;
1296
    my $skip;
1297
    my $create;
1298
1299
    print STDERR "CommitGrantTables()\n";
1300
    MySQLaccess::Debug::Print(1,"CommitGrantTables():");
1301
    
1302
    ## Make backup of original grant-tables
1303
    MySQLaccess::Debug::Print(2,"Making backup of original grant-tables...");
1304
    BackupGrantTables();
1305
1306
    ## Copy data from temporay tables to grant-tables
1307
    foreach $tbl (keys(%tables)) {
1308
       print STDERR "$tbl -> $tables{$tbl}\n";
1309
       MySQLaccess::Debug::Print(2,"Loading data $tables{$tbl} -> $tbl.");
1310
       return $nerror if ($nerror=CopyTable($tables{$tbl},$tbl));
1311
    }
1312
    return 0;
1313
}
1314
1315
1316
# ===========================================================
1317
# sub Show_Fields($table): 
1318
#  return (a reference to) a hash which holds the names
1319
#  of all relevant grant-fields, with their index in the record,
1320
#  and (a reference to) an array which holds the fieldnames.
1321
# ===========================================================
1322
sub Show_Fields {
1323
    my ($table) = @_;
1324
    my %skip = ('host' => [0,1]
1325
               ,'user' => [0,1,2]
1326
               ,'db'   => [0,1,2]
1327
               );
1328
    my %Struct = ();
1329
    my @Struct = ();
1330
    my $query = "show fields from $table;select 'ok';\n";
1331
    my $i=0;
1332
    my $line;
1333
1334
#print STDERR $query;
1335
    MySQLaccess::Debug::Print(1,"Show_Fields($table):");
1336
    MySQLaccess::Debug::Print(2,"SQL: $query");
1337
1338
    print MYSQL_Q "$query";
1339
    my $skip = <MYSQL_A>;  #skip header
1340
    while (($line=<MYSQL_A>) ne "ok\n")
1341
    {
1342
#print STDERR ">",$line;
1343
	chop($line);
1344
	MySQLaccess::Debug::Print(2," $table>: $line");
1345
	my ($field,$type,$null,$key,$default,$extra) = split(' ',$line);
1346
        $field = ucfirst($field); 
1347
	MySQLaccess::Debug::Print(3, " <split: $field - $type - $null - $key - $default - $extra");
1348
	if (! grep(/$i/,@{$skip{$table}}) ){
1349
	   $Struct{$field} = $i; #hash
1350
	   push(@Struct,$field); #array
1351
	   MySQLaccess::Debug::Print(3," ==> added column[$i]: $field ($Struct{$field})");
1352
	} 
1353
        else {
1354
           MySQLaccess::Debug::Print(3," ==> skipped column[$i], value=[$field]");
1355
        }
1356
	$i++;
1357
    }
1358
1359
    $skip=<MYSQL_A>;  # Get ok row (found already ok header)
1360
1361
    MySQLaccess::Debug::Print(2, "Array:");
1362
    foreach $field (@Struct) { MySQLaccess::Debug::Print(2,"+ $field"); }
1363
    MySQLaccess::Debug::Print(2,"Hash:");
1364
    foreach $field (keys(%Struct)) { MySQLaccess::Debug::Print(2,"+ $field -> $Struct{$field}"); }
1365
1366
    return  (\%Struct,\@Struct); 
1367
}
1368
1369
# ===========================================================
1370
# sub Show_Tables(): 
1371
#  return (a reference to) an array which holds all 
1372
#  known tables.
1373
# ===========================================================
1374
sub Show_Tables {
1375
    my @Tables = ();
1376
    my $query = "show tables;select 'ok';\n";
1377
    my $i=0;
1378
    my $line;
1379
1380
    MySQLaccess::Debug::Print(1,"Show_Tables():");
1381
    MySQLaccess::Debug::Print(2,"SQL: $query");
1382
1383
    print MYSQL_Q "$query";
1384
    my $skip = <MYSQL_A>;  #skip header
1385
    while (($line=<MYSQL_A>) ne "ok\n")
1386
    {
1387
	chop($line);
1388
	push(@Tables,$line); #array
1389
	MySQLaccess::Debug::Print(3," ==> added table: $line");
1390
    }
1391
1392
    $skip=<MYSQL_A>;  # Get ok row (found already ok header)
1393
1394
    MySQLaccess::Debug::Print(2, "Array:");
1395
    foreach $tbl (@Tables) { MySQLaccess::Debug::Print(2,"+ $tbl"); }
1396
1397
    return @Tables; 
1398
}
1399
1400
# ======================================
1401
# sub Validate_Password($passwd,$host,$user,$encpw)
1402
#  Validate the given password 
1403
#  for user '$user' 
1404
#  connecting from host '$host'
1405
# ======================================
1406
sub Validate_Password {
1407
    my ($password,$host,$user,$encpw) = @_;
1408
    my $valid=0;
1409
1410
    MySQLaccess::Debug::Print(1,"Validate_Password($password,$host,$user,$encpw)");
1411
    my $sql = "select host,user,password from user having "
1412
             ."host='$host' and user='$user' and password='$encpw' "
1413
             ."and password=PASSWORD('$password');\n";
1414
    $sql .= "select 'ok';\n";
1415
    MySQLaccess::Debug::Print(2,"SQL = $sql");
1416
    print MYSQL_Q "$sql";
1417
    
1418
    # if password is valid, at least 1 row returns before we read 'ok'
1419
    while ( ($line=<MYSQL_A>) ne "ok\n") {
1420
      MySQLaccess::Debug::Print(2," A> $line");
1421
      $valid = defined($line); 
1422
    }
1423
    my $skip = <MYSQL_A>; # read 'ok'
1424
1425
    return $valid;
1426
}
1427
1428
1429
# ==========================================================
1430
# sub Sort_fields: (rewritten by psmith)
1431
#  Build the query for an ordered list of entries
1432
# ==========================================================
1433
sub Sort_fields {
1434
  my ($start, $end, $sofar, $this, @rest) = (@_);
1435
  my @where = ("((FIELD not like '\\%') AND (FIELD <> ''))",
1436
               "((FIELD like '%\\%%') OR (FIELD like '%\\_%'))",
1437
               "(FIELD = '')");
1438
  my $res = '';
1439
1440
  $this or return ("$start $sofar $end");
1441
1442
  $sofar .= ' AND ' if $sofar;
1443
1444
  foreach $w (@where) {
1445
    my $f = $w;
1446
    $f =~ s/FIELD/$this/g;
1447
1448
    $res .= Sort_fields($start, $end, "$sofar$f", @rest);
1449
  }
1450
1451
  return ($res);
1452
}
1453
1454
# ===========================================================
1455
# sub Sort_table: (rewritten by psmith)
1456
#  return all entries in the given table,
1457
#  in an ordered fashion
1458
# ===========================================================
1459
sub Sort_table {
1460
    my ($tbl, @order) = @_;
1461
    my @res=();
1462
1463
    # as long as there's no full where clause (Distrib 3.20)...
1464
    # use having :-(
1465
    # NOTE: this clause WILL NOT work on 3.21, because of the
1466
    # order of 'ORDER BY' and 'HAVING'
1467
    my $start = "SELECT *,UCASE(host) as ucase_host FROM $tbl ";
1468
    $start   .= 'ORDER BY ' . join(',', @order) ." HAVING ";
1469
    my $end   = ";\n";
1470
1471
    # server version 3.21 has a full where clause :-)
1472
    if ($MySQLaccess::Host::SERVER >= '3.21') {
1473
    # print "+++USING FULL WHERE CLAUSE+++\n";
1474
       $start = "SELECT *,UCASE(host) as ucase_host FROM $tbl WHERE ";
1475
       $end = ' ORDER BY ' . join(',', @order) . ";\n";
1476
    }
1477
1478
    MySQLaccess::Debug::Print(1,"Sort_table():");
1479
    MySQLaccess::Debug::Print(2,"Sorting table $tbl by `@order'");
1480
1481
    my $tmp;
1482
    foreach $tmp (@order)
1483
    {
1484
      $tmp="UCASE(host)" if ($tmp eq "ucase_host");
1485
    }
1486
    my $query  = Sort_fields($start, $end, '', @order);
1487
    $query    .= "select 'ok';\n";
1488
    MySQLaccess::Debug::Print(2,"Query: $query");
1489
1490
    print MYSQL_Q "$query\n";
1491
1492
    my $delim = <MYSQL_A>; # read header
1493
    MySQLaccess::Debug::Print(3," A> $delim");
1494
    if ($delim ne "ok\n") {
1495
       if ($delim =~ /^ERROR/) {
1496
       push(@MySQLaccess::Grant::Error,'use_old_server');
1497
       MySQLaccess::Report::Print_Error_Messages() ;
1498
       exit 1;
1499
       }
1500
       while (($line=<MYSQL_A>) ne "ok\n")
1501
       {
1502
           MySQLaccess::Debug::Print(3," A> $line");
1503
           push(@res,$line);
1504
       }
1505
    }
1506
    my $skip = <MYSQL_A>; # skip result 'ok'
1507
1508
    # remove columnheaders from output
1509
    @res = grep(!/^\Q$delim\E$/, @res);
1510
    # remove trailing \n from each returned record
1511
    chomp(@res); 
1512
    # each record has 1 field to much : ucase_host
1513
    @res = grep { /(.*)\t.*$/; $_ = $1; } @res;
1514
1515
    MySQLaccess::Debug::Print(2,"Result of sorted table $tbl:");
1516
    foreach $line (@res) { MySQLaccess::Debug::Print(2," >>$line"); }
1517
    return @res;
1518
}
1519
1520
# ===========================================================
1521
# sub Get_All_db(template): 
1522
#  return all db the grant-tables are working on,
1523
#  which conform to the template
1524
# ===========================================================
1525
sub Get_All_dbs {
1526
   my ($template,$tmp) = @_;
1527
   my @db=();
1528
   my $aref;
1529
1530
   # working with  temporary tables or production tables
1531
   if (defined($tmp) and $tmp) {
1532
      $aref = \@MySQLaccess::Grant::sorted_db_tmp_table ;
1533
   }
1534
   else {
1535
      $aref = \@MySQLaccess::Grant::sorted_db_table;
1536
   }
1537
1538
   MySQLaccess::Debug::Print(1," template=[$template]");
1539
1540
   # get all db for which access-rights can be calculated,
1541
   # which conform to the template.
1542
   # !! these db's don't have to exist yet, so it's not
1543
   #    enough to look which db already exist on the system
1544
   $reg_expr = $template;
1545
   if ($template =~ /[\*\?]/) {
1546
      $reg_expr =~ tr/*?/%_/;
1547
      #$reg_expr = MySQLaccess::Wildcards::Wild2Reg($template);
1548
   }
1549
   $reg_expr = MySQLaccess::Wildcards::SQL2Reg("$reg_expr");
1550
1551
   if ( ! ($template =~ /[\*\?%_]/) ) {
1552
      push(@db,$template);
1553
      return \@db;
1554
   }
1555
1556
   MySQLaccess::Debug::Print(2,"#Reading db-table...");
1557
   foreach $record (@{$aref}) { #MySQLaccess::Grant::sorted_db_table) {
1558
    my @record=split(/\t/,$record);
1559
    my $db = $record[1];
1560
    MySQLaccess::Debug::Print(2,"> $db ");
1561
    if ( (!grep(/$db/i,@db)) and ($db =~/$reg_expr/i) ) {
1562
       push(@db,$db);
1563
       MySQLaccess::Debug::Print(2,"added");
1564
    } 
1565
    else {
1566
       MySQLaccess::Debug::Print(2,"skipped");
1567
    }
1568
   }
1569
   # if no rule is found for a certain db in the db-table,
1570
   # the rights of the user are used, so we should inform
1571
   # the user for
1572
   if (!grep(/^%$/,@db)) { push(@db,"$MySQLaccess::NEW_DB"); }
1573
   return \@db;
1574
}
1575
1576
# ===========================================================
1577
# sub Get_All_users(template): 
1578
#  return all users the grant-tables are working on,
1579
#  which conform to the template
1580
# ===========================================================
1581
sub Get_All_users {
1582
   ($template,$tmp) = @_; # nog verder uitwerken!!!
1583
   my @user=();
1584
   my $aref;
1585
1586
   # working with  temporary tables or production tables
1587
   if (defined($tmp) and $tmp) {
1588
      $aref = \@MySQLaccess::Grant::sorted_user_tmp_table ;
1589
   }
1590
   else {
1591
      $aref = \@MySQLaccess::Grant::sorted_user_table;
1592
   }
1593
1594
   MySQLaccess::Debug::Print(1,"Debug Get_All_users:");
1595
   # get all db for which access-rights can be calculated.
1596
   # !! these db's don't have to exist yet, so it's not
1597
   #    enough to look which db already exist on the system
1598
   $reg_expr = $template;
1599
   if ($template =~ /[\*\?]/) {
1600
      $reg_expr =~ tr/*?/%_/;
1601
      #$reg_expr = MySQLaccess::Wildcards::Wild2Reg($template);
1602
   }
1603
   $reg_expr = MySQLaccess::Wildcards::SQL2Reg("$reg_expr");
1604
1605
   if ( ! ($template =~ /[\*\?%_]/) ) {
1606
      push(@user,$template);
1607
      return \@user;
1608
   }
1609
1610
   MySQLaccess::Debug::Print(2,"#Reading user-table...");
1611
   foreach $record (@{$aref}) { #MySQLaccess::Grant::sorted_user_table) {
1612
    my @record=split(/\t/,$record);
1613
    my $user = $record[1];
1614
    MySQLaccess::Debug::Print(2,"> $user ");
1615
    if ( (!grep(/$user/,@user)) and ($user=~/$reg_expr/)) {
1616
       push(@user,$user);
1617
       MySQLaccess::Debug::Print(2, "added");
1618
    } 
1619
    else {
1620
       MySQLaccess::Debug::Print(2, "skipped");
1621
    }
1622
   }
1623
   # Any user means also:
1624
   # - the 'empty' user, ie without supplying a username
1625
   # - any user still to be defined/created
1626
   #push(@user,'');               #without_suplying_a_username
1627
   push(@user,"$MySQLaccess::NEW_USER");
1628
   #push(@Warnings,'minimum_priv');
1629
   return \@user;
1630
}
1631
1632
# ===========================================================
1633
# sub Get_All_hosts(template): 
1634
#  return all hosts the grant-tables are working on,
1635
#  which conform to the template
1636
# ===========================================================
1637
sub Get_All_hosts {
1638
   my ($template,$tmp) = @_;
1639
   my @host=();
1640
   my $aref;
1641
   my $aref1;
1642
1643
   # working with  temporary tables or production tables
1644
   if (defined($tmp) and $tmp) {
1645
      $aref = \@MySQLaccess::Grant::sorted_host_tmp_table ;
1646
      $aref1= \@MySQLaccess::Grant::sorted_db_tmp_table ;
1647
   }
1648
   else {
1649
      $aref = \@MySQLaccess::Grant::sorted_host_table;
1650
      $aref1= \@MySQLaccess::Grant::sorted_db_table ;
1651
   }
1652
1653
   MySQLaccess::Debug::Print(1, "Debug Get_All_hosts:");
1654
   # get all db for which access-rights can be calculated.
1655
   # !! these db's don't have to exist yet, so it's not
1656
   #    enough to look which db already exist on the system
1657
   $reg_expr = $template;
1658
   if ($template =~ /[\*\?]/) {
1659
      $reg_expr =~ tr/*?/%_/;
1660
      #$reg_expr = MySQLaccess::Wildcards::Wild2Reg($template);
1661
   }
1662
   $reg_expr = MySQLaccess::Wildcards::SQL2Reg("$reg_expr");
1663
1664
   if ( ! ($template =~ /[\*\?%_]/) ) {
1665
      push(@host,$template);
1666
      return \@host;
1667
   }
1668
1669
   MySQLaccess::Debug::Print(1, "#Reading db-table...");
1670
   foreach $record (@{$aref1}) { #MySQLaccess::Grant::sorted_db_table) {
1671
    my @record=split(/\t/,$record);
1672
    my $host = $record[0];
1673
    MySQLaccess::Debug::Print(2, "> $host ");
1674
    if (! grep(/$host/i,@host)) {
1675
       push(@host,$host);
1676
       MySQLaccess::Debug::Print(2, "added");
1677
    } 
1678
    else {
1679
       MySQLaccess::Debug::Print(2, "skipped");
1680
    }
1681
   }
1682
   MySQLaccess::Debug::Print(1, "#Reading host-table...");
1683
   foreach $record (@{$aref}) {
1684
    my @record=split(/\t/,$record);
1685
    my $host = $record[0];
1686
    MySQLaccess::Debug::Print(2, "> $host ");
1687
    if ( (!grep(/$host/,@host)) and ($host=~/$reg_expr/)) {
1688
       push(@host,$host);
1689
       MySQLaccess::Debug::Print(2, "added");
1690
    } 
1691
    else {
1692
       MySQLaccess::Debug::Print(2, "skipped");
1693
    }
1694
   }
1695
   # DOUBT:
1696
   #print "#Reading user-table...\n" if ($DEBUG>1);
1697
   #foreach $record (@MySQLaccess::Grant::sorted_user_table) {
1698
   # my @record=split(/\t/,$record);
1699
   # my $host = $record[0];
1700
   # print "> $host " if ($DEBUG>2);
1701
   # if ( (!grep(/$host/,@host)) and ($host=~/$reg_expr/)) {
1702
   #    push(@host,$host);
1703
   #    print "added\n" if ($DEBUG>2);
1704
   # } 
1705
   # else {
1706
   #    print "skipped\n" if ($DEBUG>2);
1707
   # }
1708
   #}
1709
   # Any host also means:
1710
   # - any host still to be defined/created
1711
   #push(@host,"any_other_host");
1712
1713
   @host = sort(@host);
1714
   return \@host;
1715
}
1716
1717
1718
##########################################################################
1719
package MySQLaccess::Grant;
1720
##############
1721
BEGIN {
1722
    $DEBUG     = 0;
1723
    $DEBUG     = $MySQLaccess::DEBUG unless ($DEBUG);
1724
}
1725
1726
1727
1728
# ===========================================================
1729
# sub Diff_Privileges()
1730
#  Calculate diff between temporary and original grant-tables
1731
# ===========================================================
1732
sub Diff_Privileges {
1733
   my @before=();
1734
   my @after =();
1735
   my @diffs =();
1736
1737
   # -----------------------------
1738
   # Build list of users,dbs,hosts
1739
   # to process...
1740
   my @all_dbs   = @{MySQLaccess::DB::Get_All_dbs('*')};
1741
   my @all_users = @{MySQLaccess::DB::Get_All_users('*')};
1742
   my @all_hosts = @{MySQLaccess::DB::Get_All_hosts('*')};
1743
   #if EDIT-mode
1744
   my @all_dbs_tmp   = @{MySQLaccess::DB::Get_All_dbs('*','tmp')};
1745
   my @all_users_tmp = @{MySQLaccess::DB::Get_All_users('*','tmp')};
1746
   my @all_hosts_tmp = @{MySQLaccess::DB::Get_All_hosts('*','tmp')};
1747
1748
1749
   my %Access;
1750
   # ------------------------------------
1751
   # Build list of priv. for grant-tables
1752
   foreach $host (@all_hosts) {
1753
     foreach $user (@all_users) {
1754
       foreach $db (@all_dbs) {
1755
         MySQLaccess::Grant::Initialize();
1756
         %Access = MySQLaccess::Grant::Get_Access_Rights($host,$user,$db);	
1757
         push(@before,MySQLaccess::Report::Raw_Report($host,$user,$db,\%Access));
1758
       }
1759
     }
1760
   }
1761
1762
   # ----------------------------------
1763
   # Build list of priv. for tmp-tables
1764
   foreach $host (@all_hosts_tmp) {
1765
     foreach $user (@all_users_tmp) {
1766
       foreach $db (@all_dbs_tmp) {
1767
         MySQLaccess::Grant::Initialize('tmp');
1768
         %Access = MySQLaccess::Grant::Get_Access_Rights($host,$user,$db,'tmp');	
1769
         push(@after,MySQLaccess::Report::Raw_Report($host,$user,$db,\%Access));
1770
       }
1771
     }
1772
   }
1773
1774
   # ----------------------------------
1775
   # Write results to temp-file to make
1776
   # DIFF
1777
   @before = sort(@before);
1778
   @after  = sort(@after);
1779
1780
   ($hb, $before) = tempfile("$MySQLaccess::script.XXXXXX") or
1781
    push(@MySQLaccess::Report::Errors,"Can't create temporary file: $!");
1782
   ($ha, $after)  = tempfile("$MySQLaccess::script.XXXXXX") or
1783
    push(@MySQLaccess::Report::Errors,"Can't create temporary file: $!");
1784
1785
   print $hb join("\n",@before);
1786
   print $ha join("\n",@after);
1787
   close $hb;
1788
   close $ha;
1789
1790
   # ----------------------------------
1791
   # compute difference
1792
   my $cmd="$MySQLaccess::DIFF $before $after |";
1793
   open(DIFF,"$cmd");
1794
   @diffs = <DIFF>;
1795
   @diffs = grep(/[<>]/,@diffs);
1796
   chomp(@diffs);
1797
   close(DIFF);
1798
1799
   # ----------------------------------
1800
   # cleanup temp. files
1801
   unlink($before);
1802
   unlink($after);
1803
1804
   return \@diffs;
1805
}
1806
1807
# ===========================================================
1808
# sub Initialize()
1809
#
1810
# ===========================================================
1811
sub Initialize {
1812
    %MySQLaccess::Grant::Access       = %{Default_Access_Rights()};
1813
    @MySQLaccess::Grant::Errors       = ();
1814
    @MySQLaccess::Grant::Warnings     = ();
1815
    @MySQLaccess::Grant::Notes        = ();
1816
    # -----
1817
    # rules
1818
    $MySQLaccess::Grant::Rules{'user'} = 'no_rule_found';
1819
    $MySQLaccess::Grant::Rules{'db'}   = 'no_rule_found';
1820
    $MySQLaccess::Grant::Rules{'host'} = 'no_equiv_host';
1821
    $MySQLaccess::Grant::full_access   = 1;
1822
1823
    $MySQLaccess::Grant::process_host_table = 0;
1824
    return 1;
1825
}
1826
1827
# ===========================================================
1828
# sub ReadTables()
1829
#  
1830
# ===========================================================
1831
sub ReadTables {
1832
    my ($tmp) = @_;
1833
    my ($HOST,$DB,$USER);
1834
    my @tables;
1835
1836
    # build list of available tables
1837
    @tables = MySQLaccess::DB::Show_Tables();
1838
1839
    # reading production grant-tables or temporary tables?
1840
    $tmp = (defined($tmp) and $tmp) ? 1 : 0;
1841
    if ($tmp) { #reading temporary tables
1842
       $HOST=$MySQLaccess::ACCESS_H_TMP;
1843
       $DB  =$MySQLaccess::ACCESS_D_TMP;
1844
       $USER=$MySQLaccess::ACCESS_U_TMP;
1845
1846
       # ----------------------------
1847
       # do tables exist?
1848
       if (!grep(/$HOST/,@tables)) { MySQLaccess::DB::CreateTable($HOST); }
1849
       if (!grep(/$USER/,@tables)) { MySQLaccess::DB::CreateTable($USER); }
1850
       if (!grep(/$DB/,@tables))   { MySQLaccess::DB::CreateTable($DB); }
1851
1852
       MySQLaccess::Debug::Print(1,"Finding fields in tmp-ACL files:");
1853
       # -----------------------------
1854
       # Get record-layout 
1855
       my ($h1,$h2) = MySQLaccess::DB::Show_Fields($HOST);
1856
       my ($d1,$d2) = MySQLaccess::DB::Show_Fields($DB);
1857
       my ($u1,$u2) = MySQLaccess::DB::Show_Fields($USER);
1858
       %MySQLaccess::Grant::H_tmp = %{$h1}; @MySQLaccess::Grant::H_tmp = @{$h2};
1859
       %MySQLaccess::Grant::D_tmp = %{$d1}; @MySQLaccess::Grant::D_tmp = @{$d2};
1860
       %MySQLaccess::Grant::U_tmp = %{$u1}; @MySQLaccess::Grant::U_tmp = @{$u2};
1861
1862
#       @MySQLaccess::Grant::Privileges_tmp=@{Make_Privlist()};
1863
#
1864
       MySQLaccess::Debug::Print(1, "Reading sorted temp-tables:");
1865
       @MySQLaccess::Grant::sorted_db_tmp_table  = MySQLaccess::DB::Sort_table($DB, 'ucase_host', 'user', 'db');
1866
       @MySQLaccess::Grant::sorted_host_tmp_table= MySQLaccess::DB::Sort_table($HOST, 'ucase_host', 'db');
1867
       @MySQLaccess::Grant::sorted_user_tmp_table= defined($MySQLaccess::Param{'password'}) ?
1868
                           MySQLaccess::DB::Sort_table($USER, 'ucase_host', 'user', 'password'):
1869
                           MySQLaccess::DB::Sort_table($USER, 'ucase_host', 'user');
1870
    }
1871
    else {      #reading production grant-tables
1872
       $HOST=$MySQLaccess::ACCESS_H;
1873
       $DB  =$MySQLaccess::ACCESS_D;
1874
       $USER=$MySQLaccess::ACCESS_U;
1875
1876
       MySQLaccess::Debug::Print(1,"Finding fields in ACL files:");
1877
       # -----------------------------
1878
       # Get record-layout 
1879
       my ($h1,$h2) = MySQLaccess::DB::Show_Fields($HOST);
1880
       my ($d1,$d2) = MySQLaccess::DB::Show_Fields($DB);
1881
       my ($u1,$u2) = MySQLaccess::DB::Show_Fields($USER);
1882
       %MySQLaccess::Grant::H = %{$h1}; @MySQLaccess::Grant::H = @{$h2};
1883
       %MySQLaccess::Grant::D = %{$d1}; @MySQLaccess::Grant::D = @{$d2};
1884
       %MySQLaccess::Grant::U = %{$u1}; @MySQLaccess::Grant::U = @{$u2};
1885
1886
       @MySQLaccess::Grant::Privileges=@{Make_Privlist()};
1887
1888
       MySQLaccess::Debug::Print(1, "Reading sorted tables:");
1889
       @MySQLaccess::Grant::sorted_db_table  = MySQLaccess::DB::Sort_table($DB, 'ucase_host', 'user', 'db');
1890
       @MySQLaccess::Grant::sorted_host_table= MySQLaccess::DB::Sort_table($HOST, 'ucase_host', 'db');
1891
       @MySQLaccess::Grant::sorted_user_table= defined($MySQLaccess::Param{'password'}) ?
1892
                           MySQLaccess::DB::Sort_table($USER, 'ucase_host', 'user', 'password'):
1893
                           MySQLaccess::DB::Sort_table($USER, 'ucase_host', 'user');
1894
    }
1895
1896
    return 0;
1897
}
1898
1899
# ===========================================================
1900
# sub Get_Access_Rights(host,user,db)
1901
#  report the access_rights for the tuple ($host,$user,$db).
1902
# ===========================================================
1903
sub Get_Access_Rights {
1904
  local ($host,$user,$db,$tmp) = @_;
1905
1906
   my $aref_user;
1907
   my $aref_host;
1908
   my $aref_db;
1909
   # working with  temporary tables or production tables
1910
   if (defined($tmp) and $tmp) {
1911
      $aref_user = \@MySQLaccess::Grant::sorted_user_tmp_table;
1912
      $aref_host = \@MySQLaccess::Grant::sorted_host_tmp_table;
1913
      $aref_db   = \@MySQLaccess::Grant::sorted_db_tmp_table;   
1914
   }
1915
   else {
1916
      $aref_user = \@MySQLaccess::Grant::sorted_user_table;
1917
      $aref_host = \@MySQLaccess::Grant::sorted_host_table;
1918
      $aref_db   = \@MySQLaccess::Grant::sorted_db_table; 
1919
   }
1920
1921
1922
  my ($refrecord,$refgrant);
1923
  my ($_host_,$_user_,$encpw_);
1924
  my %_Access_;
1925
1926
  MySQLaccess::Debug::Print(1, "for ($host,$user,$db):");  
1927
1928
  # ******************************************************************************
1929
  # Create default access-rights
1930
  #   default access-rights are no access at all!!
1931
1932
1933
  # ******************************************************************************
1934
  # get hostname for IP-address
1935
  # get IP-address for hostname
1936
  local $host_name = MySQLaccess::Host::IP2Name($host);
1937
  local $host_ip   = MySQLaccess::Host::Name2IP($host);
1938
1939
  MySQLaccess::Debug::Print(3,"host=$host, hostname=$host_name, host-ip =$host_ip");
1940
  MySQLaccess::Debug::Print(3,"user=$user");
1941
  MySQLaccess::Debug::Print(3,"db  =$db");
1942
1943
  # ***********************************************************************
1944
  # retrieve information on USER
1945
  #  check all records in mysql::user for matches with the tuple (host,user)
1946
  # ***********************************************************************
1947
  #    4.OR (add) the privileges for the user from the "user" table.
1948
  #     (add all privileges which is "Y" in "user")
1949
  ($refrecord,$refgrant)    = Get_grant_from_user($host,$user,$aref_user);
1950
  ($_host_,$_user_,$encpw_) = @{$refrecord};
1951
  %_access_                 = %{$refgrant};
1952
1953
  foreach $field (keys(%U)) { ##only priv. set in user-table
1954
    $MySQLaccess::Grant::Access{$field} = ($MySQLaccess::Grant::Access{$field} or $_access_{$field});
1955
  }
1956
1957
  if ($_user_ eq $MySQLaccess::NEW_USER) { 
1958
     push(@Warnings,'minimum_priv');
1959
  }
1960
  if ($_user_ ne $user) {
1961
     $user=$_user_;
1962
     push(@Warnings,'anonymous_access');
1963
  }
1964
1965
  # *******************************************************
1966
  #  Validate password if this has been asked to do
1967
  # *******************************************************
1968
  if (defined($password)) {
1969
     $valid = Validate_Password($password,$_host_,$_user_,$_encpw_,$aref_user);
1970
     if (!$valid) { push(@Errors,'invalid_password'); }
1971
     else         { push(@Notes,'valid_password'); }
1972
  }
1973
1974
  # ******************************************************************************
1975
  # retrieve information on DB
1976
  #  check all records in mysql::db for matches with the triple (host,db,user)
1977
  #  first match is used.
1978
  # ******************************************************************************
1979
  #    2.Get grant for user from the "db" table.
1980
1981
  ($refrecord,$refgrant)=Get_grant_from_db($host,$db,$user,$aref_db); #set process_host_table
1982
  ($_host_,$_user_,$encpw_) = @{$refrecord};
1983
  %_access_                 = %{$refgrant};
1984
1985
  foreach $field (keys(%D)) { ##only priv. set in db-table
1986
    $MySQLaccess::Grant::Access{$field} = ($MySQLaccess::Grant::Access{$field} or $_access_{$field});
1987
  }
1988
1989
  # ***********************************************************************
1990
  # retrieve information on HOST
1991
  #  check all records in mysql::host for matches with the tuple (host,db)
1992
  #
1993
  #  ' The host table is mainly to maintain a list of "secure" servers. '
1994
  # ***********************************************************************
1995
  #    3.If hostname is "empty" for the found entry, AND the privileges with
1996
  #      the privileges for the host in "host" table.
1997
  #      (Remove all which is not "Y" in both)
1998
1999
  if ($MySQLaccess::Grant::process_host_table) {
2000
     ($refrecord,$refgrant)=Get_grant_from_host($host,$db,$aref_host);
2001
     ($_host_,$_user_,$encpw_) = @{$refrecord};
2002
     %_access_                 = %{$refgrant};
2003
2004
     foreach $field (keys(%H)) {  ##only priv. set in host-table 
2005
       $MySQLaccess::Grant::Access{$field} = ($MySQLaccess::Grant::Access{$field} and $_access_{$field});
2006
     } 
2007
  }
2008
2009
  MySQLaccess::Debug::Print(1,"done for ($host,$user,$db)");
2010
  return %MySQLaccess::Grant::Access;
2011
}
2012
2013
# ####################################
2014
# FINDING THE RIGHT GRANT-RULE
2015
# ==========================================================
2016
# sub Get_grant_from_user:
2017
# ==========================================================
2018
sub Get_grant_from_user {
2019
  my ($host,$user,$aref) = @_;
2020
2021
  MySQLaccess::Debug::Print(1, "");
2022
  MySQLaccess::Debug::Print(1, "(host=$host,user=$user)");
2023
2024
  my %Access_user = %{Default_Access_Rights()}; 
2025
2026
  my $rule_found=0;
2027
  my @record = ();
2028
  my $record;
2029
2030
  foreach $record (@{$aref}) {
2031
    $MySQLaccess::Grant::full_access=0;
2032
    MySQLaccess::Debug::Print(3, "Record= $record");
2033
    @record=split(/\t/,$record);
2034
2035
    # check host and db
2036
    # with possible wildcards in field
2037
    # replace mysql-wildcards by reg-wildcards
2038
    my $host_tpl = MySQLaccess::Wildcards::SQL2Reg($record[0]);
2039
    my $user_tpl = $record[1]; #user field isn't pattern-matched!!
2040
    my $passwd   = $record[2];
2041
2042
    MySQLaccess::Debug::Print(3, "=>host_tpl : read=$record[0] -> converted=$host_tpl");
2043
    MySQLaccess::Debug::Print(3, "=>user_tpl : read=$record[1] -> $user_tpl");
2044
    MySQLaccess::Debug::Print(3, "=>password : read=$record[2] -> $passwd");
2045
2046
2047
    if ( MySQLaccess::Host::MatchTemplate($host,$host_tpl) and
2048
         MySQLaccess::Wildcards::MatchTemplate($user_tpl,$user)
2049
       ) 
2050
    {
2051
  MySQLaccess::Debug::Print(2, "FOUND!!");
2052
        if ($passwd eq '') { push(@Warnings,'insecure_user');  }
2053
        else               { push(@Notes,'password_required'); }
2054
2055
        foreach $field (keys(%U)) {
2056
          $Access_user{$field} = $MySQLaccess::Report::Answer{$record[$U{$field}]};
2057
        }
2058
        #print "\n" if $DEBUG;
2059
        $MySQLaccess::Grant::Rules{'user'} = $record;
2060
        $rule_found=1;
2061
        last;
2062
    }
2063
  }
2064
2065
  # -------------------------------
2066
  #  setting privileges to user-priv
2067
  MySQLaccess::Debug::Print(2, "Rights after parsing user-table..:");
2068
  if (! $rule_found ) {
2069
     @record=();
2070
     MySQLaccess::Debug::Print(2, "NO record found in the user-table!!");
2071
  }
2072
  else {
2073
     MySQLaccess::Debug::Print(2, "Selected record=@record");
2074
     MySQLaccess::Debug::Print(2, "<=?=> $record");
2075
  }
2076
 
2077
  MySQLaccess::Debug::Print(1, "returning @record");
2078
2079
  return (\@record,\%Access_user); #matching record in user-table
2080
}
2081
2082
# ==========================================================
2083
# sub Get_grant_from_db:
2084
# ==========================================================
2085
sub Get_grant_from_db {
2086
  my ($host,$db,$user,$aref) = @_;
2087
2088
  MySQLaccess::Debug::Print(1, "(host=$host,user=$user,db=$db)");
2089
2090
  my %Access_db    = %{Default_Access_Rights()};
2091
  my $rule_found=0;
2092
2093
  foreach $record (@{$aref}) {
2094
    $full_access=0;
2095
    MySQLaccess::Debug::Print(2, "Read db: $record");
2096
    @record=split(/\t/,$record);
2097
2098
    # check host and db
2099
    # with possible wildcards in field
2100
    # replace mysql-wildcards by reg-wildcards
2101
    my $host_tpl = MySQLaccess::Wildcards::SQL2Reg($record[0]);
2102
    my $db_tpl   = MySQLaccess::Wildcards::SQL2Reg($record[1]);
2103
    my $user_tpl = $record[2]; #user field isn't pattern matched!!
2104
    MySQLaccess::Debug::Print(3, "=>host_tpl : read=$record[0] -> converted=$host_tpl");
2105
    MySQLaccess::Debug::Print(3, "=>db_tpl   : read=$record[1] -> $db_tpl");
2106
    MySQLaccess::Debug::Print(3, "=>user_tpl : read=$record[2] -> $user_tpl");
2107
2108
    if ( ( MySQLaccess::Host::Is_localhost($host_tpl)
2109
           or  MySQLaccess::Wildcards::MatchTemplate($host_tpl,$host_name)
2110
           or  MySQLaccess::Wildcards::MatchTemplate($host_tpl,$host_ip) )
2111
         and ( MySQLaccess::Wildcards::MatchTemplate($db_tpl,$db) )
2112
         and ( MySQLaccess::Wildcards::MatchTemplate($user_tpl,$user) ) ) {
2113
 
2114
      $MySQLaccess::Grant::process_host_table = ($record[0] eq '');
2115
2116
      if ($user_tpl eq '') { push(@Warnings,'public_database'); }
2117
2118
      foreach $field (keys(%D)) {
2119
        $Access_db{$field} = $MySQLaccess::Report::Answer{$record[$D{$field}]};
2120
      }
2121
      $rule_found=1;
2122
      $MySQLaccess::Grant::Rules{'db'} = $record;
2123
      last;
2124
    }
2125
  }
2126
2127
  # -------------------------------
2128
  #  setting privileges to db-priv
2129
  MySQLaccess::Debug::Print(2, "Rights after parsing db-table..:");
2130
  if (! $rule_found ) {
2131
    MySQLaccess::Debug::Print(2, "NO rule found in db-table => no access granted!!");
2132
  }
2133
2134
  return (\@record,\%Access_db);
2135
}
2136
2137
# ==========================================================
2138
# sub Get_grant_from_host:
2139
# ==========================================================
2140
sub Get_grant_from_host {
2141
  my ($host,$db,$aref) = @_;
2142
2143
  MySQLaccess::Debug::Print(1, "Get_grant_from_host()");
2144
2145
  my %Access_host = %{Default_Access_Rights()};
2146
2147
  # the host-table doesn't have to be processed if the host-field
2148
  # in the db-table isn't empty
2149
  if (!$MySQLaccess::Grant::process_host_table) {
2150
    MySQLaccess::Debug::Print(2, ">> Host-table doesn't have to be processed!!");
2151
    $MySQLaccess::Grant::Rules{'host'} = 'no_equiv_host';
2152
    return ([],\%Access_host);
2153
  }
2154
2155
  my $rule_found=0;
2156
  my @record = ();
2157
2158
  foreach $record (@{$aref}) {
2159
    $full_access=0;
2160
    MySQLaccess::Debug::Print(2, "host: $record");
2161
    @record=split(/\t/,$record);
2162
2163
    # check host and db
2164
    # with possible wildcards in field
2165
    # replace mysql-wildcards by reg-wildcards
2166
    my $host_tpl = MySQLaccess::Wildcards::SQL2Reg($record[0]);
2167
    my $db_tpl   = MySQLaccess::Wildcards::SQL2Reg($record[1]);
2168
    MySQLaccess::Debug::Print(3, "=>host_tpl : $record[0] -> $host_tpl");
2169
    MySQLaccess::Debug::Print(3, "=>db_tpl   : $record[1] -> $db_tpl");
2170
2171
    if ( ( MySQLaccess::Host::Is_localhost($host_tpl)
2172
           or MySQLaccess::Wildcards::MatchTemplate($host_tpl,$host_name)
2173
           or MySQLaccess::Wildcards::MatchTemplate($host_tpl,$host_ip) )
2174
         and ( MySQLaccess::Wildcards::MatchTemplate($db_tpl,$db) ) ) {
2175
2176
      $MySQLaccess::Grant::Rules{'host'} = $record;
2177
      $rule_found=1;
2178
      foreach $field (keys(%H)) {
2179
        $Access_host{$field} = $MySQLaccess::Report::Answer{$record[$H{$field}]};
2180
      }
2181
      last;
2182
    }
2183
  }
2184
2185
  # -------------------------------
2186
  #  setting privileges to host-priv
2187
  MySQLaccess::Debug::Print(2, "Rights after parsing host-table..:");
2188
  if (! $rule_found ) {
2189
     @record=();
2190
     MySQLaccess::Debug::Print(2, "NO restrictions found in the host-table!!");
2191
  }
2192
2193
  # --------------------------------
2194
  # debugging access-rights in db 
2195
2196
  return (\@record,\%Access_host); #matching record in host-table
2197
}
2198
2199
2200
2201
# ===========================================================
2202
# sub Default_Access_Rights():
2203
#  return (a reference to) a hash which holds all default
2204
#  priviliges currently defined in the grant-tables.
2205
# ===========================================================
2206
sub Default_Access_Rights {
2207
    my %right = ();
2208
2209
    MySQLaccess::Debug::Print(2, "Debug Default_Access_Rights():");
2210
    # add entry for all fields in the HOST-table
2211
    foreach $field (keys(%MySQLaccess::Grant::H)) {
2212
	$right{$field}='0' unless (defined($right{$field}));
2213
    }
2214
    # add entry for all fields in the DB-table
2215
    foreach $field (keys(%MySQLaccess::Grant::D)) {
2216
	$right{$field}='0' unless (defined($right{$field}));
2217
    }
2218
    # add entry for all fields in the USER-table
2219
    foreach $field (keys(%MySQLaccess::Grant::U)) {
2220
	$right{$field}='0' unless (defined($right{$field}));
2221
    }
2222
    # --------------
2223
    # debugging info
2224
    foreach $field (keys(%right)) { MySQLaccess::Debug::Print(3, sprintf("> %15s : %1s",$field,$right{$field})); }
2225
2226
    return \%right;
2227
}
2228
2229
# ======================================
2230
# sub Make_Privlist
2231
#  Make an ordered list of the privileges
2232
#  that should be reported
2233
# ======================================
2234
sub Make_Privlist {
2235
    # layout:
2236
    #'select_priv',     'create_priv',
2237
    #'insert_priv',     'drop_priv',
2238
    #'update_priv',     'reload_priv',
2239
    #'delete_priv',     'process_priv',
2240
    #'file_priv',       'shutdown_priv');
2241
    my $right;
2242
    my @privlist=();
2243
    foreach $right (@U) {
2244
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
2245
    };
2246
    foreach $right (@D) {
2247
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
2248
    };
2249
    foreach $right (@H) {
2250
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
2251
    };
2252
#       print "Privileges:\n";
2253
#       foreach $field (@privlist) { print " > $field\n"; }
2254
    return \@privlist;
2255
}
2256
2257
2258
2259
########################################################################
2260
package MySQLaccess::Report;
2261
use Exporter ();
2262
@EXPORT = qw(&Print_Header());
2263
BEGIN {
2264
    $FORM = $ENV{'SCRIPT_NAME'};
2265
    $DEBUG     = 0;
2266
    $DEBUG     = $MySQLaccess::DEBUG unless ($DEBUG);
2267
2268
    # translation-table for poss. answers
2269
    %Answer =  ('Y' =>  1 , 'N' =>  0
2270
               , 1  => 'Y',  0  => 'N'
2271
               ,'?' => '?', ''  => '?'
2272
               );
2273
    $headers   = 0;
2274
    $separator = 0;
2275
2276
# ****************************
2277
# Notes and warnings
2278
%MESSAGES = ( 
2279
  'insecure_user' 
2280
   => "Everybody can access your DB as user `\$user' from host `\$host'\n"
2281
     ."WITHOUT supplying a password.\n"
2282
     ."Be very careful about it!!"
2283
 ,'password_required' 
2284
   => "A password is required for user `\$user' :-("
2285
 ,'invalid_password'
2286
   => "The password '\$password' for user `\$user' is invalid :-P"
2287
 , 'valid_password'
2288
   => "You supplied the right password for user `\$user' :-)"
2289
 ,'public_database' 
2290
   => "Any user with the appropriate permissions has access to your DB!\n"
2291
     ."Check your users!"
2292
 ,'full_access' 
2293
   => "All grant-tables are empty, which gives full access to ALL users !!"
2294
 ,'no_rule_found'
2295
   => "No matching rule"
2296
 ,'no_equiv_host' 
2297
   => "Not processed: host-field is not empty in db-table."
2298
 ,'least_priv'
2299
   => "If the final priveliges of the user are more then you gave the user,\n"
2300
     ."check the priveliges in the db-table `\$db'."
2301
 ,'minimum_priv'
2302
   => "The privileges for any new user are AT LEAST\n"
2303
     ."the ones shown in the table above,\n"
2304
     ."since these are the privileges of the db `\$db'.\n"
2305
 ,'not_found_mysql'
2306
   => "The MySQL client program <$MySQLaccess::MYSQL> could not be found.\n"
2307
     ."+ Check your path, or\n"
2308
     ."+ edit the source of this script to point \$MYSQL to the mysql client.\n"
2309
 ,'not_found_mysqldump'
2310
   => "The MySQL dump program <$MySQLaccess::MYSQLDUMP> could not be found.\n"
2311
     ."+ Check your path, or\n"
2312
     ."+ edit the source of this script to point \$MYSQLDUMP to the mysqldump program.\n"
2313
 ,'not_found_diff'
2314
   => "The diff program <$MySQLaccess::DIFF> could not be found.\n"
2315
     ."+ Check your path, or\n"
2316
     ."+ edit the source of this script to point \$DIFF to the diff program.\n"
2317
 ,'Unrecognized_option'
2318
   => "Sorry,\n"
2319
     ."You are using an old version of the mysql-program,\n"
2320
     ."which does not yet implement a neccessary option.\n"
2321
     ."\n"
2322
     ."You need at least Version 6.2 of the mysql-client,\n"
2323
     ."which was build in MySQL v3.0.18, to use this version\n"
2324
     ."of `$MySQLaccess::script'."
2325
 ,'Access_denied'
2326
   => "Sorry,\n"
2327
     ."An error occured when trying to connect to the database\n"
2328
     ."with the grant-tables:\n"
2329
     ."* Maybe YOU do not have READ-access to this database?\n"
2330
     ."* If you used the -U option, you may have supplied an invalid username?\n"
2331
     ."  for the superuser?\n"
2332
     ."* If you used the -U option, it may be possible you have to supply\n"
2333
     ."  a superuser-password to, with the -P option?\n"
2334
     ."* If you used the -P option, you may have supplied an invalid password?\n"
2335
 ,'Dbaccess_denied'
2336
   => "Sorry,\n"
2337
     ."An error occured when trying to connect to the database\n"
2338
     ."with the grant-tables. (dbaccess denied)\n"
2339
 ,'Unknown_tmp_table'
2340
   => "Sorry,\n"
2341
     ."An error occured when trying to work with the temporary tables in the database\n"
2342
     ."with the grant-tables. (One of the temporary tables does not exist)\n"
2343
 ,'Unknown_table'
2344
   => "Sorry,\n"
2345
     ."An error occured when trying to work with some tables in the database\n"
2346
     ."with the grant-tables. (table does not exist)\n"
2347
 ,'use_old_server'
2348
   => "Sorry,\n"
2349
     ."An error occured when executing an SQL statement.\n"
2350
     ."You might consider altering the use of the parameter `--old_server' when \n"
2351
     ."calling `$MySQLaccess::script'."
2352
 ,'unknown_error'
2353
   => "Sorry,\n"
2354
     ."An error occured when trying to connect to the database\n"
2355
     ."with the grant-tables. (unknown error)\n"
2356
 ,'anonymous_access'
2357
   => "Accessing the db as an anonymous user.\n"
2358
     ."Your username has no relevance\n"
2359
 ,'user_required'
2360
   => "You have to supply a userid."
2361
 ,'db_required'
2362
   => "You have to supply the name of a database."
2363
 ,'host_required'
2364
   => "You have to supply the name of a host."
2365
 );
2366
2367
2368
} 
2369
# =====================================
2370
# sub Print_Header:
2371
#  print header info
2372
# =====================================
2373
sub Print_Header {
2374
    if ($MySQLaccess::CMD) { #command-line mode
2375
    print "$MySQLaccess::script Version $MySQLaccess::VERSION\n"
2376
         ."By RUG-AIV, by Yves Carlier (Yves.Carlier\@rug.ac.be)\n"
2377
         ."Changes by Steve Harvey (sgh\@vex.net)\n"
2378
         ."This software comes with ABSOLUTELY NO WARRANTY.\n";
2379
    }
2380
    if ($MySQLaccess::CGI) { #CGI-BIN mode
2381
    print "content-type: text/html\n\n" 
2382
       . "<HTML>\n"
2383
         ."<HEAD>\n"
2384
         ."<TITLE>MySQLaccess</TITLE>\n"
2385
         ."</HEAD>\n"
2386
         ."<BODY>\n"
2387
         ."<H1>$MySQLaccess::script Version $MySQLaccess::VERSION</H1>\n" 
2388
         ."<CENTER>\n<ADDRESS>\n"
2389
         ."By RUG-AIV, by Yves Carlier (<a href=mailto:Yves.Carlier\@rug.ac.be>Yves.Carlier\@rug.ac.be</a>)<BR>\n"
2390
         ."Changes by Steve Harvey (<a href=mailto:sgh\@vex.net>sgh\@vex.net</a>)<BR>\n"
2391
         ."This software comes with ABSOLUTELY NO WARRANTY.<BR>\n"
2392
         ."</ADDRESS>\n</CENTER>\n"
2393
         ."<HR>\n";
2394
    Print_Taskbar();
2395
    print "<HR>\n";
2396
    }
2397
    return 1;
2398
}
2399
2400
# =====================================
2401
# sub Print_Footer:
2402
#  print footer info
2403
# =====================================
2404
sub Print_Footer {
2405
    if ($MySQLaccess::CMD) { #command-line mode
2406
    print "\n"
2407
         ."BUGs can be reported by email to bugs\@mysql.com\n";
2408
    }
2409
    if ($MySQLaccess::CGI) { #CGI-BIN mode
2410
    if ($MySQLaccess::Param{'brief'}) {
2411
    print "</table>\n";  #close table in brief-output
2412
    }
2413
    print "<HR>\n"
2414
         ."<ADDRESS>\n"
2415
         ."BUGs can be reported by email to <a href=mailto:bugs\@mysql.com>bugs\@mysql.com</a><BR>\n"
2416
#         ."Don't forget to mention the version $VERSION!<BR>\n"
2417
         ."</ADDRESS>\n"
2418
         ."</BODY>\n"
2419
         ."</HTML>\n";
2420
    }
2421
    return 1;
2422
}
2423
2424
# =====================================
2425
# sub Print_Taskbar:
2426
#  print taskbar on STDOUT
2427
# =====================================
2428
sub Print_Taskbar {
2429
    print "<CENTER>\n"
2430
         ."[<a href=$FORM?relnotes=on>Release&nbsp;Notes</a>] \n"
2431
         ."[<a href=$FORM?version=on>Version</a>] \n"
2432
         ."[<a href=$FORM?plan=on>Future&nbsp;Plans</a>] \n"
2433
         ."[<a href=$FORM?howto=on>Examples</a>] \n"
2434
         ."[<a href=$FORM?help=on>New check</a>] \n"
2435
         ."[<a href=$FORM?edit=on>Change/edit ACL</a>] \n"
2436
         ."</CENTER>\n";
2437
    return 1;
2438
}
2439
2440
# =====================================
2441
# sub Print_Form:
2442
#  print CGI-form
2443
# =====================================
2444
sub Print_Form {
2445
print <<EOForm;
2446
<center>
2447
<!-- Quering -->
2448
<FORM method=POST action=$FORM>
2449
2450
<table border width="100%" >
2451
<tr>
2452
  <th>MySQL server</th>
2453
  <th>User information</th>
2454
  <th>Reports</th>
2455
  </tr>
2456
2457
<tr>
2458
  <td valign=top>
2459
  <table>
2460
  <tr>
2461
    <td halign=right><b>Host</b><br><font size=-2>(Host on which MySQL-server resides.)</font></td>
2462
    <td valign=top><INPUT name=rhost type=text size=15 maxlength=15 value="$MySQLaccess::Param{'rhost'}"></td>
2463
    </tr>
2464
  <tr>
2465
    <td halign=right><b>Superuser</b><br><font size=-2>(User which has <font color="Red">read-access</font> to grant-tables.)</font></td>
2466
    <td valign=top><INPUT name=superuser type=text size=15 maxlength=15 value="$MySQLaccess::Param{'superuser'}"></td>
2467
    </tr>
2468
  <tr>
2469
    <td halign=right><b>Password</b><br><font size=-2>(of Superuser.)</font></td>
2470
    <td valign=top><INPUT name=spassword type=password size=15 maxlength=15 value="$MySQLaccess::Param{'spassword'}"></td>
2471
    </tr>
2472
  </table>
2473
  </td>
2474
2475
  <td valign=top>
2476
  <table>
2477
  <tr>
2478
    <td halign=right><b><font color=Red>User</font></b><br><font size=-2>(Userid used to connect to MySQL-database.)</font></td>
2479
    <td halign=top><INPUT name=user type=text size=15 maxlength=15 value="$MySQLaccess::Param{'user'}"></td>
2480
    </tr>
2481
  <tr>
2482
    <td halign=right><b>Password</b><br><font size=-2>(Password user has to give to get access to MySQL-database.)</font></td>
2483
    <td valign=top><INPUT name=password type=password size=15 maxlength=15 value="$MySQLaccess::Param{'password'}"></td>
2484
    </tr>
2485
  <tr>
2486
    <td halign=right><b><font color=Red>Database</font></b><br><font size=-2>(Name of MySQL-database user tries to connect to.</font><br><font size=-2>Wildcards <font color="Green">(*,?,%,_)</font> are allowed.)</font></td>
2487
    <td valign=top><INPUT name=db type=text size=15 maxlength=15 value="$MySQLaccess::Param{'db'}"></td>
2488
    </tr>
2489
  <tr>
2490
    <td halign=right><b>Host</b><br><font size=-2>(Host from where the user is trying to connect to MySQL-database.</font><br><font size=-2>Wildcards <font color="Green">(*,?,%,_)</font> are allowed.)</font></td>
2491
    <td valign=top><INPUT name=host type=text size=15 maxlength=15 value="$MySQLaccess::Param{'host'}"></td>
2492
    </tr>
2493
  </table>
2494
  </td>
2495
2496
  <td valign=center>
2497
  <table cellspacing=5 cellpadding=2 cols=1 height="100%">
2498
  <tr align=center>
2499
    <td halign=right><INPUT type=submit name=brief value="Brief"><br>
2500
                     <INPUT type=submit name=table value="Tabular"></td>
2501
    </tr>
2502
  <tr align=center>
2503
    <td></td>
2504
    </tr>
2505
  <tr align=center>
2506
    <td halign=right><INPUT type=reset value="Clear"></td>
2507
    </tr>
2508
  </table>
2509
  </td>
2510
  </tr>
2511
2512
</table>
2513
</form>
2514
2515
2516
</BODY>
2517
</HTML>
2518
EOForm
2519
    return 1;
2520
}
2521
2522
# =====================================
2523
# sub Print_Usage:
2524
#  print some information on STDOUT
2525
# =====================================
2526
sub Print_Usage {
2527
    Print_Error_Messages();
2528
    if ($MySQLaccess::CMD) { #command-line mode
2529
        Print_Options();
2530
    }
2531
    if ($MySQLaccess::CGI) { #CGI-BIN mode
2532
        Print_Form();
2533
    }    
2534
    return 1;
2535
}
2536
2537
# ======================================
2538
# sub Print_Version:
2539
# ======================================
2540
sub Print_Version {
2541
    if ($MySQLaccess::CMD) {
2542
       print $MySQLaccess::INFO;
2543
    }
2544
    if ($MySQLaccess::CGI) { 
2545
       print "<PRE>\n"; 
2546
       print $MySQLaccess::INFO;
2547
       print "</PRE>\n"; 
2548
    }
2549
    return 1;
2550
}
2551
2552
# ======================================
2553
# sub Print_Relnotes:
2554
# ======================================
2555
sub Print_Relnotes {
2556
    if ($MySQLaccess::CMD) {
2557
       print $MySQLaccess::RELEASE;
2558
    }
2559
    if ($MySQLaccess::CGI) { 
2560
       print "<PRE>\n";
2561
       print $MySQLaccess::RELEASE;
2562
       print "</PRE>\n"; 
2563
    }
2564
    return 1;
2565
}
2566
2567
# ======================================
2568
# sub Print_Plans:
2569
# ======================================
2570
sub Print_Plans {
2571
    if ($MySQLaccess::CMD) {
2572
       print $MySQLaccess::TODO;
2573
    }
2574
    if ($MySQLaccess::CGI) { 
2575
       print "<PRE>\n";
2576
       print $MySQLaccess::TODO;
2577
       print "</PRE>\n"; 
2578
    }
2579
    return 1;
2580
}
2581
2582
# ======================================
2583
# sub Print_HowTo:
2584
# ======================================
2585
sub Print_HowTo {
2586
    if ($MySQLaccess::CMD) {
2587
       print $MySQLaccess::HOWTO;
2588
    }
2589
    if ($MySQLaccess::CGI) { 
2590
       print "<PRE>\n"; 
2591
       print $MySQLaccess::HOWTO;
2592
       print "</PRE>\n"; 
2593
    }
2594
    return 1;
2595
}
2596
2597
# ======================================
2598
# sub Print_Options:
2599
# ======================================
2600
sub Print_Options {
2601
    if ($MySQLaccess::CGI) { print "<PRE>\n"; }
2602
    print $MySQLaccess::OPTIONS;
2603
    if ($MySQLaccess::CGI) { print "</PRE>\n"; }
2604
    return 1;
2605
}
2606
2607
# ======================================
2608
# sub Print_Error_Access:
2609
# ======================================
2610
sub Print_Error_Access {
2611
    my ($error) = @_;
2612
    print "\n";
2613
    if ($MySQLaccess::CGI) { print "<font color=Red>\n<PRE>\n"; }
2614
    print $MESSAGES{$error};
2615
    if ($MySQLaccess::CGI) { print "</PRE>\n</font>\n"; }
2616
    print "\n";
2617
    return 1;
2618
}
2619
2620
# ======================================
2621
# sub Print_Error_Messages:
2622
# ======================================
2623
sub Print_Error_Messages {
2624
#    my ($error) = @_;
2625
    print "\n";
2626
    if ($MySQLaccess::CGI) { print "<font color=Red>\n<center>\n"; }
2627
    foreach $error (@MySQLaccess::Grant::Error) {
2628
       print $MESSAGES{$error};
2629
       print $MySQLaccess::CGI ? "<br>\n" : "\n";
2630
    }
2631
    if ($MySQLaccess::CGI) { print "</center>\n</font>\n"; }
2632
    print "\n";
2633
    return 1;
2634
}
2635
2636
# ======================================
2637
# sub Print_Message:
2638
# ======================================
2639
sub Print_Message {
2640
    my ($aref) = @_;
2641
    my @messages = @{$aref};
2642
    print "\n";
2643
    if ($MySQLaccess::CGI) { print "<font color=DarkGreen>\n<center>\n"; }
2644
    foreach $msg (@messages) {
2645
       print $msg;
2646
       print $MySQLaccess::CGI ? "<br>\n" : "\n";
2647
    }
2648
    if ($MySQLaccess::CGI) { print "</center>\n</font>\n"; }
2649
    print "\n";
2650
    return 1;
2651
}
2652
2653
# ======================================
2654
# sub Print_Edit:
2655
# ======================================
2656
sub Print_Edit {
2657
    print "\n";
2658
    if (!$MySQLaccess::CGI) { 
2659
       print "Note: Editing the temporary tables is NOT supported in CMD-line mode!\n";
2660
       return 0;
2661
    }
2662
    print "<CENTER>\n"
2663
         ."<form action=$FORM method=GET>\n"
2664
         ."<table width=90% border>\n"
2665
         ."<tr>\n"
2666
         ." <td><input type=checkbox name=copy value=on> Copy grant-rules to temporary tables<br></td>\n"
2667
         ." <td rowspan=5 align=center valign=center><input type=submit value=Go></td>\n"
2668
         ."</tr>\n"
2669
         ."<tr>\n"
2670
         ." <td> Edit temporary tables with external application:<br>"
2671
         ." <a href=\"$MySQLaccess::MYSQLADMIN\">$MySQLaccess::MYSQLADMIN</a></td>\n"
2672
         ."</tr>\n"
2673
         ."<tr>\n"
2674
         ." <td><input type=checkbox name=preview value=on> Preview changes made in temporary tables</td>\n"
2675
         ."</tr>\n"
2676
         ."<tr>\n"
2677
         ." <td><input type=checkbox name=commit value=on> Make changes permanent</td>\n"
2678
         ."</tr>\n"
2679
         ."<tr>\n"
2680
         ." <td><input type=checkbox name=rollback value=on> Restore previous grand-rules</td>\n"
2681
         ."</tr>\n"
2682
         ."<tr>\n"
2683
         ." <td colspan=2 align=center><font size=-2 color=Red>You need write,delete and drop-privileges to perform the above actions</font></td>\n"
2684
         ."</tr>\n"
2685
         ."</table>\n"
2686
         ."</form>\n"
2687
         ."</CENTER>\n";
2688
2689
    return 1;
2690
}
2691
2692
2693
# ======================================
2694
# sub Print_Access_rights:
2695
#  print the access-rights on STDOUT
2696
# ======================================
2697
sub Print_Access_rights {
2698
    my ($host,$user,$db,$refhash) = @_;
2699
2700
    if (defined($MySQLaccess::Param{'brief'})) { 
2701
#       if ($MySQLaccess::CGI) { print "<PRE>\n"; }
2702
       Matrix_Report($host,$user,$db,$refhash);  
2703
#       if ($MySQLaccess::CGI) { print "</PRE>\n"; }
2704
    }
2705
    else { 
2706
       Tabular_Report($host,$user,$db,$refhash); 
2707
       $MySQLaccess::Report::separator = $MySQLaccess::CGI ? "<hr>" : "-"x80;
2708
    }
2709
    return 1;
2710
}
2711
2712
# ======================================
2713
# sub Print_Diff_ACL:
2714
#  print the diff. in the grants before and after
2715
# ======================================
2716
sub Print_Diff_ACL {
2717
    my ($aref) = @_;
2718
    my @diffs = @{$aref};
2719
    my %block = ( '<' => 'Before',
2720
                  '>' => 'After',
2721
                );
2722
    my %color = ( '<' => 'Green',
2723
                  '>' => 'Red',
2724
                ); 
2725
    my $curblock = '';
2726
2727
    # -----------------------------
2728
    # create column-headers
2729
    foreach $field (@MySQLaccess::Grant::Privileges) {
2730
      push(@headers,substr($field,0,4));
2731
    }
2732
2733
    if ($MySQLaccess::CMD) {
2734
    print "\n";
2735
    print "Differences in access-rights BEFORE and AFTER changes in grant-tables\n";
2736
#    print "---------------------------------------------------------------------\n";
2737
      my $line1="";
2738
      my $line2="";
2739
      $line1 .= sprintf("| %-30s|",'Host,User,DB');
2740
      $line2 .= sprintf("+-%-30s+",'-' x 30);
2741
      foreach $header (@headers) {
2742
        $line1 .= sprintf("%-4s|",$header);
2743
        $line2 .= sprintf("%s+",'----');
2744
      }
2745
      print "$line2\n";
2746
      print "$line1\n";
2747
      print "$line2\n";
2748
2749
      $format = "format STDOUT = \n"
2750
              . "^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< " . " @|||" x 10 ."\n"
2751
              . '$host_user_db,@priv' . "\n"
2752
              . ".\n";
2753
#print $format;
2754
      eval $format;
2755
    }
2756
    if ($MySQLaccess::CGI) {
2757
    print "<table border width=100%>\n";
2758
    print "<tr>\n";
2759
    print "<th colspan=11>";
2760
    print "Differences in access-rights <font color=$color{'<'}>BEFORE</font> "
2761
         ."and <font color=$color{'>'}>AFTER</font> changes to grant-tables</font>\n";
2762
    print "</th>";
2763
    print "</tr>\n";
2764
    print "<tr>\n";    
2765
    $line1 .= sprintf("<th>%-20s</th>",'Host, User, DB');
2766
    foreach $header (@headers) {
2767
      $line1 .= sprintf("<th>%-4s</th>",$header);
2768
    }
2769
    print "$line1</tr>\n";
2770
    }
2771
2772
    foreach $line (@diffs) {
2773
        $type = substr($line,0,1);
2774
        $line = substr($line,1);
2775
        ($host,$user,$db,@priv) = split(/,/,$line);
2776
        if ($MySQLaccess::CMD) {
2777
           if ($type ne $curblock) {
2778
              $curblock = $type;
2779
              print $block{$curblock},":\n";
2780
           }
2781
           #print "$line\n";
2782
           write;
2783
        }
2784
        if ($MySQLaccess::CGI) {
2785
           if ($type ne $curblock) {
2786
              $curblock = $type;
2787
              print "<tr><td><b>$block{$curblock}<b></td></tr>\n";
2788
           }
2789
           $line1="<td><font color=$color{$type}>$host, $user, $db</font></td>";
2790
           foreach $field (@priv) {
2791
              $line1 .= sprintf("<td align=center><font color=$color{$type}>%-4s</font></td>",$field);
2792
           }
2793
           print "<tr>$line1</tr>\n";
2794
        }
2795
    }
2796
    print      "\n";
2797
    if ($MySQLaccess::CMD) {
2798
    print "---------------------------------------------------------------------\n";
2799
    }
2800
    if ($MySQLaccess::CGI) {
2801
    print      "</table><br>";
2802
    }
2803
2804
2805
    return 1;
2806
}
2807
2808
# ======================================
2809
# sub Tabular_Report
2810
#  Tabular report,
2811
#  suitable for 1 triple (host,db,user)
2812
# ======================================
2813
sub Tabular_Report {
2814
    my ($host,$user,$db,$a) = @_;
2815
    my $column=2;
2816
2817
    # -----------------------------
2818
    # separator
2819
    if ($MySQLaccess::Report::separator) { print "$MySQLaccess::Report::separator\n"; }
2820
    
2821
    # -----------------------------
2822
    # print table of access-rights
2823
    my $rows = int(@MySQLaccess::Grant::Privileges/2);  #round up
2824
    my @table=();
2825
    $j=0;
2826
    for $i (0 .. $rows-1) {
2827
      $table[$j]=$MySQLaccess::Grant::Privileges[$i];
2828
      $j = $j+2;
2829
    }
2830
    $j=1;
2831
    for $i ($rows .. $#MySQLaccess::Grant::Privileges) {
2832
      $table[$j]=$MySQLaccess::Grant::Privileges[$i];
2833
      $j = $j+2;
2834
    }
2835
    if ($MySQLaccess::CMD) {
2836
    print "\n";
2837
    print "Access-rights\n";
2838
    print "for USER '$user', from HOST '$host', to DB '$db'\n";
2839
    }
2840
    if ($MySQLaccess::CGI) {
2841
    print "<table border width=100%>\n";
2842
    print "<tr>\n";
2843
    }
2844
    if ($MySQLaccess::CGI) {
2845
    print "<th colspan=5>";
2846
    print "<font color=Red>Access-rights</font>\n";
2847
    print "for USER '<font color=Green>$user</font>', from HOST '<font color=Green>$host</font>', to DB '<font color=Green>$db</font>'\n";
2848
    print "</th>";
2849
    print "</tr>\n";
2850
    print "<tr>\n";
2851
    }
2852
    if ($MySQLaccess::CMD) {
2853
    print      "\t+-----------------+---+\t+-----------------+---+";
2854
    }
2855
    foreach $field (@table) {
2856
        if ($MySQLaccess::CMD) {
2857
          if ($column==2) { print "\n\t"; $column=1;}
2858
          else            { print "\t";   $column=2;}
2859
          printf "| %-15s | %s |",$field,$Answer{$a->{$field}}; 
2860
        }
2861
        if ($MySQLaccess::CGI) {
2862
          if ($column==2) { print "</tr>\n<tr>\n"; $column=1;}
2863
          else            { print "<td width=10%></td>";   $column=2;}
2864
          printf " <td width=35%><b>%-15s</b></td><td width=10%>%s</td>\n",$field,$Answer{$a->{$field}}; 
2865
        }
2866
    }
2867
    print      "\n";
2868
    if ($MySQLaccess::CMD) {
2869
    print      "\t+-----------------+---+\t+-----------------+---+\n";
2870
    }
2871
    if ($MySQLaccess::CGI) {
2872
    print      "</tr>\n</table><br>";
2873
    }
2874
2875
    # ---------------
2876
    # print notes:
2877
    foreach $note (@MySQLaccess::Grant::Notes) {
2878
      my $message = $MESSAGES{$note};
2879
      $message =~ s/\$user/$user/g; 
2880
      $message =~ s/\$db/$db/g;
2881
      $message =~ s/\$host/$host/g;
2882
      $message =~ s/\$password/$password/g;
2883
      $PREFIX='NOTE';
2884
      if ($MySQLaccess::CMD) {
2885
      my @lines = split(/\n/,$message);
2886
      foreach $line (@lines) { 
2887
        print "$PREFIX:\t $line\n"; 
2888
        $PREFIX='    ';
2889
      }
2890
      }
2891
      if ($MySQLaccess::CGI) {
2892
      print "<b>$PREFIX:</b> $message<br>\n";
2893
      }
2894
    } 
2895
2896
    # ---------------
2897
    # print warnings:
2898
    foreach $warning (@MySQLaccess::Grant::Warnings) {
2899
      my $message = $MESSAGES{$warning};
2900
      $message =~ s/\$user/$user/g;
2901
      $message =~ s/\$db/$db/g;
2902
      $message =~ s/\$host/$host/g;
2903
      $message =~ s/\$password/$password/g;
2904
      $PREFIX='BEWARE';
2905
      if ($MySQLaccess::CMD) {
2906
      my @lines = split(/\n/,$message);
2907
      foreach $line (@lines) { 
2908
        print "$PREFIX:\t $line\n"; 
2909
        $PREFIX='      ';
2910
      }
2911
      }
2912
      if ($MySQLaccess::CGI) {
2913
      print "<b>$PREFIX:</b> $message<br>\n";
2914
      }
2915
    }
2916
2917
    # ---------------
2918
    # print errors:
2919
    foreach $error (@MySQLaccess::Grant::Errors) {
2920
      my $message = $MESSAGES{$error};
2921
      $message =~ s/\$user/$user/g;
2922
      $message =~ s/\$db/$db/g;
2923
      $message =~ s/\$host/$host/g;
2924
      $message =~ s/\$password/$password/g;
2925
      $PREFIX='ERROR';
2926
      if ($MySQLaccess::CMD) {
2927
      my @lines = split(/\n/,$message);
2928
      foreach $line (@lines) { 
2929
        print "$PREFIX:\t $line\n"; 
2930
        $PREFIX='    ';
2931
      }
2932
      }
2933
      if ($MySQLaccess::CGI) {
2934
      print "<b>$PREFIX:</b> $message<br>\n";
2935
      }
2936
    }
2937
2938
    # ---------------
2939
    # inform if there are no rules ==> full access for everyone.
2940
    if ($MySQLaccess::Grant::full_access) { print "$MESSAGES{'full_access'}\n"; }
2941
2942
    # ---------------
2943
    # print the rules used
2944
    print "\n";
2945
    if ($MySQLaccess::CMD) {
2946
    print "The following rules are used:\n";
2947
    foreach $field (sort(keys(%MySQLaccess::Grant::Rules))) {
2948
      my $rule = (defined($MESSAGES{$MySQLaccess::Grant::Rules{$field}}) ? $MESSAGES{$MySQLaccess::Grant::Rules{$field}} : $MySQLaccess::Grant::Rules{$field});
2949
      $rule =~ s/\t/','/g;
2950
      printf " %-5s : '%s'\n",$field,$rule;
2951
    }
2952
    }
2953
    if ($MySQLaccess::CGI) {
2954
    print "<br>\n";
2955
    print "<table border width=100%>\n";
2956
    print "<tr><th colspan=2>The following rules are used:</th></tr>\n";
2957
    foreach $field (sort(keys(%MySQLaccess::Grant::Rules))) {
2958
      my $rule = (defined($MESSAGES{$MySQLaccess::Grant::Rules{$field}}) ? $MESSAGES{$MySQLaccess::Grant::Rules{$field}} : $MySQLaccess::Grant::Rules{$field});
2959
      $rule =~ s/\t/','/g;
2960
      printf "<tr><th>%-5s</th><td>'%s'</td></tr>\n",$field,$rule;
2961
    }
2962
    print "</table>\n";
2963
    }
2964
 
2965
    return 1;
2966
}
2967
2968
# ======================================
2969
# sub Matrix_Report:
2970
#  single-line output foreach triple,
2971
#  no notes,warnings,...
2972
# ======================================
2973
sub Matrix_Report {
2974
    my ($host,$user,$db,$a) = @_;
2975
    my @headers = ();
2976
    
2977
    if (! $headers) {
2978
       # -----------------------------
2979
       # create column-headers
2980
       foreach $field (@MySQLaccess::Grant::Privileges) {
2981
         push(@headers,substr($field,0,4));
2982
       }
2983
    
2984
       # -----------------------------
2985
       # print column-headers
2986
       print "\n";
2987
       if ($MySQLaccess::CMD) {
2988
         my $line1="";
2989
         my $line2="";
2990
         foreach $header (@headers) {
2991
           $line1 .= sprintf("%-4s ",$header);
2992
           $line2 .= sprintf("%s ",'----');
2993
         }
2994
         $line1 .= sprintf("| %-20s",'Host,User,DB');
2995
         $line2 .= sprintf("+ %-20s",'-' x 20);
2996
         print "$line1\n";
2997
         print "$line2\n";
2998
       }
2999
       if ($MySQLaccess::CGI) {
3000
         print "<table width=100% border>\n";
3001
         my $line1="<tr>";
3002
         foreach $header (@headers) {
3003
           $line1 .= sprintf("<th>%-4s</th>",$header);
3004
         }
3005
         $line1 .= sprintf("<th>%-20s</th>",'Host, User, DB');
3006
         print "$line1</tr>\n";
3007
       }
3008
3009
       # ----------------------------
3010
       # column-headers should only be 
3011
       # printed once.
3012
       $MySQLaccess::Report::headers=1;
3013
    }
3014
3015
    # ------------------------
3016
    # print access-information
3017
    if ($MySQLaccess::CMD) {
3018
      foreach $field (@MySQLaccess::Grant::Privileges) {
3019
  	  printf " %-2s  ",$Answer{$a->{$field}}; 
3020
      }
3021
      printf "| %-20s",join(',',$host,$user,$db);
3022
      print "\n";
3023
    }
3024
    if ($MySQLaccess::CGI) {
3025
      print "<tr>";
3026
      foreach $field (@MySQLaccess::Grant::Privileges) {
3027
  	  printf "<td align=center>%-2s</td>",$Answer{$a->{$field}}; 
3028
      }
3029
      printf "<td><b>%-20s</b></td>",join(', ',$host,$user,$db);
3030
      print "</tr>\n";
3031
    }
3032
3033
    return 1;
3034
}
3035
3036
3037
# ======================================
3038
# sub Raw_Report:
3039
#  single-line output foreach triple,
3040
#  no notes,warnings,...
3041
# ======================================
3042
sub Raw_Report {
3043
    my ($host,$user,$db,$a) = @_;
3044
    my @headers = ();
3045
    my $string = "";
3046
    
3047
    # ------------------------
3048
    # print access-information
3049
    $string = "$host,$user,$db,";
3050
    foreach $field (@MySQLaccess::Grant::Privileges) {
3051
	  $string .= $Answer{$a->{$field}} . ","; 
3052
    }
3053
    return $string;
3054
}
3055
3056
3057
#######################################################################
3058
package MySQLaccess::Wildcards;
3059
BEGIN {
3060
    $DEBUG     = 0;
3061
    $DEBUG     = $MySQLaccess::DEBUG unless ($DEBUG);
3062
}
3063
# ############################################
3064
# SQL, WILDCARDS and REGULAR EXPRESSIONS 
3065
# ============================================
3066
# translage SQL-expressions to Reg-expressions
3067
# ============================================
3068
sub SQL2Reg {
3069
    my ($expr) = @_;
3070
    my $expr_o = $expr;
3071
    $expr  =~ s/\./\\./g;
3072
    $expr  =~ s/\\%/\002/g;
3073
    $expr  =~ s/%/.*/g;
3074
    $expr  =~ s/\002/%/g;
3075
    $expr  =~ s/\\_/\002/g;
3076
    $expr  =~ s/_/.+/g;
3077
    $expr  =~ s/\002/_/g;
3078
    MySQLaccess::Debug::Print(2,"$expr_o --> $expr");
3079
    return $expr;
3080
}
3081
3082
# translage WILDcards to Reg-expressions
3083
# ============================================
3084
sub Wild2Reg {
3085
    my ($expr) = @_;
3086
    my $expr_o = $expr;
3087
    $expr  =~ s/\./\\./g;
3088
    $expr  =~ s/\\\*/\002/g;
3089
    $expr  =~ s/\*/.*/g;
3090
    $expr  =~ s/\002/*/g;
3091
    $expr  =~ s/\\\?/\002/g;
3092
    $expr  =~ s/\?/.+/g;
3093
    $expr  =~ s/\002/?/g;
3094
    MySQLaccess::Debug::Print(2,"$expr_o --> $expr");
3095
    return $expr;
3096
}
3097
3098
# =============================================
3099
# match a given string with a template
3100
# =============================================
3101
sub MatchTemplate {
3102
    my ($tpl,$string) = @_;
3103
    my $match=0;
3104
    if ($string=~ /^$tpl$/ or $tpl eq '') { $match=1; }
3105
    else                                  { $match=0;}
3106
    MySQLaccess::Debug::Print(2,"($tpl,$string) --> $match");
3107
    return $match;
3108
}
3109
3110
#######################################################################
3111
package MySQLaccess::Host;
3112
BEGIN {
3113
    $localhost = undef;
3114
    $DEBUG     = 2;
3115
    $DEBUG     = $MySQLaccess::DEBUG unless ($DEBUG);
3116
}
3117
# ======================================
3118
# sub IP2Name
3119
#  return the Name with the corr. IP-nmbr
3120
#  (no aliases yet!!)
3121
# ======================================
3122
sub IP2Name {
3123
    my ($ip) = @_;
3124
    my $ip_o = $ip;
3125
    if ($ip !~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/o) {
3126
       MySQLaccess::Debug::Print(3,"'$ip' is not an ip-number, returning IP=$ip");
3127
       return $ip;
3128
    }
3129
    MySQLaccess::Debug::Print(4,"IP=$ip split up => $1.$2.$3.$4");
3130
    $ip = pack "C4",$1,$2,$3,$4;
3131
    MySQLaccess::Debug::Print(4,"IP packed -> >>$ip<<\n");
3132
    my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyaddr($ip, AF_INET);
3133
    MySQLaccess::Debug::Print(3,"IP=$ip_o => hostname=$name");
3134
    MySQLaccess::Debug::Print(4,"aliases=$aliases");
3135
    MySQLaccess::Debug::Print(4,"addrtype=$addrtype - length=$length");
3136
    return ($name || $ip);
3137
    #return ($name || undef);
3138
}
3139
3140
# ======================================
3141
# sub Name2IP
3142
#  return the IP-number of the host
3143
# ======================================
3144
sub Name2IP {
3145
    my ($name) = @_;
3146
    if ($name =~ /[%_]/) { 
3147
       MySQLaccess::Debug::Print(3,"'$name' contains SQL-wildcards, returning name=$name");
3148
       return $name; 
3149
    }
3150
    my ($_name,$aliases,$addrtype,$length,@addrs) = gethostbyname($name);
3151
    my ($a,$b,$c,$d) = unpack('C4',$addrs[0]);
3152
    my $ip = "$a.$b.$c.$d";
3153
    MySQLaccess::Debug::Print(3,"hostname=$name => IP=$ip");
3154
    MySQLaccess::Debug::Print(4,"aliases=$aliases");
3155
    MySQLaccess::Debug::Print(4,"addrtype=$addrtype - length=$length");
3156
    #if ($ip ne "") { return "$ip"; }
3157
    #else           { return undef; }
3158
    return ($ip || $name);
3159
}
3160
3161
# ========================================
3162
# sub LocalHost
3163
#  some special action has to be taken for
3164
#  the localhost
3165
# ========================================
3166
sub LocalHost {
3167
    if (!defined($MySQLaccess::Host::localhost)) {
3168
       $MySQLaccess::Host::localhost = Sys::Hostname::hostname();
3169
       MySQLaccess::Debug::Print(3,"Setting package variable \$localhost=$MySQLaccess::Host::localhost");
3170
    }
3171
    my $host = $localhost;
3172
    MySQLaccess::Debug::Print(3,"localhost = $host");
3173
    return $host;
3174
}
3175
3176
# ========================================
3177
# check if the given hostname (or ip)
3178
# corresponds with the localhost
3179
# ========================================
3180
sub Is_localhost {
3181
    my ($host_tpl) = @_;
3182
    my $isit = 0;
3183
    if (($MySQLaccess::host_name eq $localhost) or ($MySQLaccess::host_ip eq $local_ip)) {
3184
	MySQLaccess::Debug::Print(2,"Checking for localhost");
3185
      MySQLaccess::Debug::Print(3,"because ($MySQLaccess::host_name EQ $localhost) AND ($MySQLaccess::host_ip EQ $local_ip)");
3186
      $isit = ( 'localhost' =~ /$host_tpl/ ) ? 1 : 0;
3187
      MySQLaccess::Debug::Print(3," 'localhost' =?= $host_tpl  -> $isit");
3188
      return $isit;
3189
    }
3190
    else {
3191
      MySQLaccess::Debug::Print(4,"Not checking for localhost");
3192
      MySQLaccess::Debug::Print(4,"because ($MySQLaccess::host_name != $localhost) AND ($MySQLaccess::host_ip != $local_ip)");
3193
      return 0;
3194
    }
3195
}
3196
3197
3198
# =========================================
3199
# check if host (IP or name) can be matched
3200
# on the template.
3201
# =========================================
3202
sub MatchTemplate {
3203
    my ($host,$tpl) = @_;
3204
    my $match = 0;
3205
   
3206
    MySQLaccess::Debug::Print(1, "($host) =?= ($tpl)");
3207
3208
    my $host_name = IP2Name($host);
3209
    my $host_ip   = Name2IP($host);
3210
3211
    MySQLaccess::Debug::Print(2, "name=$host_name ; ip=$host_ip");
3212
    $match = (MySQLaccess::Wildcards::MatchTemplate($tpl,$host_name) or
3213
             MySQLaccess::Wildcards::MatchTemplate($tpl,$host_ip));
3214
3215
    MySQLaccess::Debug::Print(2, "($host_name,$host_ip) =?= ($tpl): $ncount");
3216
3217
    return $match;
3218
}
3219
3220
########################################################################
3221
package MySQLaccess::Debug;
3222
BEGIN {
3223
   my $dbg_file = "$MySQLaccess::script_log";
3224
   open(DEBUG,"> $dbg_file") or warn "Could not open outputfile $dbg_file for debugging-info\n";
3225
   select DEBUG;
3226
   $| = 1;
3227
   select STDOUT;
3228
}
3229
# =========================================
3230
# Print debugging information on STDERR
3231
# =========================================
3232
sub Print {
3233
    my ($level,$mesg) = @_;
3234
    my ($pack,$file,$line,$subname,$hasargs,$wantarray) = caller(1);
3235
    my ($PACK)  = split('::',$subname); 
3236
    my $DEBUG = ${$PACK."::DEBUG"} ? ${$PACK."::DEBUG"} : $MySQLaccess::DEBUG ;
3237
    my ($sec,$min,$hour) = localtime();
3238
    print DEBUG "[$hour:$min:$sec $subname] $mesg\n" if ($DEBUG>=$level);
3239
}
3240