SVN auf DEBIAN 9 (Stretch) erstellen

Installation und Einrichten von SVN auf DEBIAN 9

Zunächst das Paket SVN installieren:

apt-get install svn
apt-get install libapache2-mod-svn subversion-tools

Dann erstellen wir ein leeres Repository:

mkdir /var/lib/svn

svnadmin create /var/lib/svn

Noch den Zugriff für Apache einrichten:

chown -R www-data:www-data /var/lib/svn/

Zugang mit User/Passwort konfigurieren:

Der erste User:

htpasswd -cm /etc/apache2/dav_svn.passwd admin

jeder weitere:

htpasswd -m /etc/apache2/dav_svn.passwd stduser

Die Zugriffsart konfigurieren:

vim /etc/apache2/dav_svn.authz

Inhalt:

[/]
* = r
admin = rw
stduser = rw

Subversion Daemon starten:

# svnserve -d -r /var/lib/svn/

Optional die Apache Module aktivieren

# a2enmod dav
# a2enmod dav_svn
# a2enmod authz_svn

Damit die Zugriffe auch über http (bzw. https) erfolgen können muss noch die Datei /etc/apache2/mods-available/dav_svn.conf wie folgt angepasst werden:

DAV svn
SVNPath /var/lib/svn
AuthType Basic
AuthName „Subversion Repository“
AuthUserFile /etc/apache2/dav_svn.passwd
AuthzSVNAccessFile /etc/apache2/dav_svn.authz
Require valid-user
## zum Aktivieren von SSL
# SSLRequireSSL

 

Apache neu starten

#$ /etc/init.d/apache2 force-reload

Die URL lautet dann
http://yourserverip/svn/

Test an der Console:

#$ cd SVNrepo1
$ touch test.txt
#$ echo HELLO >test.txt
#$ svn add test.txt
>A test.txt

svn commit -m ‚First Revision‘
Authentication realm: <http://localhost:1080> Subversion Repository

In eclipse über die Perspective „SVN Repository Exploring“ auschecken.

SMTP Fake Server (JAVA)

Kommt man in die Verlegenheit, in  einem isolierten Netzwerk ein  Mail verschickendes Modul entwickeln oder Testen zu müssen,  jedoch aber kein SMTP-Server verfügbar ist, hilft dieses kleine Tool:

Fake SMTP Server, hier runterzuladen:

http://nilhcem.com/FakeSMTP/index.html

In Benutzung sieht es dann über das grafische Logfenster so aus:

grafisches Logfenster von FAKE SMTP Server
Logfenster von FAKE SMTP Server

Der Aufruf lautet:

java -jar fakeSMTP-2.0.jar

Auf obigem Logfenster kann man genau sehen was passiert ist, Doppelklick öffnet sogar die Mail im Mailclient, das eventuell mitgeschickte Attachment kann angeschaut / extrahiert/ gespeichert werden. Sehr nützlich.

 

Nach Doppelklick:

Ansicht in Mailclient

Dadurch kann man die volle Funktionalität testen, das Attachment in Vorschau nehmen oder drucken,

Auf der Commandline ist eine Art Log zu sehen.

Für den Entwickler sieht man die Details im Klartext auf dem Tab

Tab Last Message - Klartextdarstellung incl. MIME
Tab Last Message – Klartextdarstellung incl. MIME

 

Fazit: Die eigene Software besteht dadurch den Entwicklertest und im Integrationsenvironment steht später ein echter SMTP Server zur Verfügung.

 

Soundex in deutsch

Die hervorragende Funktion SOUNDEX für phonetische Ähnlichkeit ist auf die englische Sprache optimiert.
Es gibt jedoch auch diverse Quellen für eine deutsche Implemantation.

Hier jetzt zunächst nur die Verweise:

https://www.doag.org/formes/pubfiles/5428921/20140128-regio-fr-meisriemler-Koelner_Phonetik.pdf

Teaser für diesen Link der DOAG:

select distinct NAME1,
SYS.UTL_MATCH.EDIT_DISTANCE(‚MEIER‘, NAME1) Distanz
from tab_kunden
where func_phonetik(name1) = func_phonetik(‚MEIER‘)
order by 2 desc, 1

https://github.com/deezaster/germanphonetic

Nicht direkt Oracle aber viele Details:

German Soundex „Kölner Phonetik“ SQL implementation

Index Rebuild (Wartungsjob oder interaktiv)

Auf dem  Monitoringserver trat ein Problem auf,  durch die hohe Changerate der Daten (ständig kommen neue hinzu, und im Idealfall werden alte gelöscht) ist nach einer gewissen Zeit die Indexfragmentierungsrate recht hoch ist. Die ist zwar nur ein theoretischer Wert, jedoch führt das in der Praxis tatsächlich zu höheren Laufzeiten bei einfachen Abfragen.

Im Extremfall dauerte die Abfrage (count(*)) gegen eine leere Tabelle Minuten. Da kann zwar auch ein korrupter Block im Spiel gewesen sein, jedoch ist das dann nur ein weiterer Grund für Heilung durch einen Index Rebuild, welcher dann letztlich das Problem löst.

Aus dieser Notwendigkeit sind mehrere Jobs entstanden.

Zunächst ein Job, der den akuten Befund „Abfrage auf Tabelle dauert viel zu lange“ ermittelt und einen Index Rebuild durchführt.

Dann entstand ein Job, der als Kriterium für den Reorg nicht eine vielleicht durch Seiteneffekte beeinflusste Laufzeit, sondern direkt die in die INDEX_STATS ermittelte Fragmentierung als Kriterium heranzieht.

Die übliche empfohlene Schwelle für einen Rebuild  ist ein Fragmentierungsgrad > 20%. Wir fanden teilweise 85% Fragmentierung vor.

( siehe MyOracleSupport How Btree Indexes Are Maintained (Doc ID 30405.1) )

File 2 -> INDEXWARTUNG_MITANALYZE.SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
SET SERVEROUTPUT ON SIZE 1000000

/*
Analysiert für einzugebendes SCHEMA die Fragmentierunge der Indizes und wirft einen INDEX REBUILD ONLINE an. Dies hat sich als relativ sinnfrei erwiesen, da der ANALYZE schon die Tabelle lockt - und länger braucht als der Rebuild.  So machen wir lieber einen Wartungsjob ohne Analyze mit nur Rebuild online! (s.u)

Hier eine kleine Feinheit:
Der Index wird in zwei Schritten analysiert, erst ONLINE, soll den Cache füllen,
dann ohne ONLINE, erst dann wird INDEX_STATS gefüllt, was dann schneller erfolgt
Leider sind die Objektgrößen hier im Monitor Schema jenseits gut & böse...,
führt leider auch zu Locks, trotz gefülltem Cache
*/


DECLARE

USERNAM VARCHAR2(30);
INPUTSCHEMA VARCHAR2(30);

NIND_NAME DBA_INDEXES.INDEX_NAME%TYPE; -- VARCHAR(30);
NTAB_NAME DBA_INDEXES.TABLE_NAME%TYPE; --VARCHAR(30);
FRAG VARCHAR(10);
CMD VARCHAR2(500);
CMD_ONLINE VARCHAR2(512);

EXAMINE_ONLY NUMBER;
REPORT_STAT NUMBER;
NLOOP NUMBER;
LINES NUMBER;
FragRate NUMBER;
Iname VARCHAR(30);
DelRows INDEX_STATS.lf_rows%TYPE; --NUMBER
TabRows INDEX_STATS.lf_rows%TYPE; --NUMBER

BEGIN
DBMS_OUTPUT.PUT_LINE('-- INPUTSCHEMA MUSS!! GEQUOTED EINGEGEBEN WERDEN ');
USERNAM := &&INPUTSCHEMA;

EXAMINE_ONLY := &&NODELETE;

/* 0 ==> FALSE; 1 ==> TRUE */
REPORT_STAT := 1;
DBMS_OUTPUT.PUT_LINE('-- Reportmode ist '|| REPORT_STAT);
NLOOP := 0;

-- Hauptschleife über die INDIZES
FOR NIM_IND IN ( SELECT index_name, table_name FROM dba_indexes
WHERE OWNER = USERNAM AND Index_type = 'NORMAL')
LOOP

NIND_NAME := NIM_IND.index_name;
NTAB_NAME := NIM_IND.table_name;

NLOOP := NLOOP + 1;
DBMS_OUTPUT.PUT_LINE( NLOOP || ': INDEX ' || NIND_NAME);

-- nicht zählen nur "ist mindestens eine Zeile da"
CMD := 'SELECT COUNT(*) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME || ' WHERE ROWNUM <= 1)';

EXECUTE IMMEDIATE CMD INTO LINES;

DBMS_OUTPUT.PUT_LINE(NTAB_NAME || ' ZEILEN : '||LINES);

IF LINES > 0 THEN

--- Something to do?

CMD := 'ANALYZE INDEX ' || USERNAM || '.' || NIND_NAME || ' VALIDATE STRUCTURE';
CMD_ONLINE := CMD || ' ONLINE';

DBMS_OUTPUT.PUT_LINE(CMD);

-- Beschleunigung des Locking Statements durch Füllen des Caches
EXECUTE IMMEDIATE CMD_ONLINE;
EXECUTE IMMEDIATE CMD;

SELECT name, lf_rows , del_lf_rows
INTO Iname, TabRows , DelRows
FROM index_stats
WHERE name = NIND_NAME;

IF DELRows > 0 THEN
FragRate := 100*DelRows/Tabrows;
Frag := TO_CHAR((100*DelRows)/TabRows,'999.9');

IF REPORT_STAT <> 0 THEN
DBMS_OUTPUT.PUT_LINE(Iname || ' Rows: ' || TabRows || ' DelRows: '|| DelRows
|| ' Fragmented: ' || Frag);
END IF;

IF FragRate > 20 THEN

CMD := 'ALTER INDEX "' || USERNAM || '"."' || NIND_NAME || '" rebuild ONLINE';

IF REPORT_STAT <> 0 THEN
DBMS_OUTPUT.PUT_LINE(' ****** Fragmentierungsgrad ' || Frag || ' zu hoch, Index Reorg ist erforderlich ');
END IF;

IF EXAMINE_ONLY = 0 THEN
DBMS_OUTPUT.PUT_LINE(' REORG CMD: ' || CMD);
EXECUTE IMMEDIATE CMD;
COMMIT;
END IF;

END IF; -- FragRate

ELSE --DELROWS
DBMS_OUTPUT.PUT_LINE(Iname || ' 0 gelöschte Zeilen -> keine Fragmentierung ');
FragRate := 0;
END IF; -- DELROWS

END IF; -- LINES

END LOOP; -- NUM_TABLES;

END;
/
EXIT

Leider ist das Ermitteln der validen Indexstruktur maßgeblich teurer als meist der einfach Reorg.

(einleuchtende Erklärung https://blog.pythian.com/analyze-index-validate-structure-dark-side/)

Als Zwischenlösung gibt es einen einfachen Trick der im obigen Script angewandt wurde.

Zunächst ein

analyze index ix validate structure online;

der die Tabelle nicht lockt, jedoch auch INDEX_STATS nicht füllt, gefolgt von einem

analyze index ix validate structure;

DIe Hoffnung ist, jetzt sind die Daten im Cache und der die Tabelle lockende ANALYZE dauert nicht so lange.

Da dies jedoch mit den überaus großen Objekten des Monitoringsschemas unter Produktionsbedingungen nicht funktioniert hat,  letztlich ein Job der einfach alle Indizes eines Schemas neu aufbaut.

Dies kann interaktiv erfolgen, oder je nach Bedarf in einem Zyklus. Ein Wartungsfenster ist nicht erforderlich, da der Job die Objekte nicht lockt. Davon würde Job2 profitieren, in einem Wartungsfenster ohne produktiven Betrieb wäre ein VALIDATE STRUCTUR als Kriterium des Rebuilds hilfreich, und dieser könnte ohne Lock durchgeführt werden.

Doch jetzt der Bulk-Job:

Script 3 -> INDEXWARTUNG_BULK.SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
SET SERVEROUTPUT ON SIZE 1000000

/*
Wartungsjob: für einzugebendes SCHEMA wird ein INDEX REBUILD ONLINE angeworfen.
Dies ist bei Objekten deren Analyse teuer ist, die effektiveste Variante.

*/


DECLARE

USERNAM VARCHAR2(30);
INPUTSCHEMA VARCHAR2(30);

NIND_NAME DBA_INDEXES.INDEX_NAME%TYPE; -- VARCHAR(30);
NTAB_NAME DBA_INDEXES.TABLE_NAME%TYPE; --VARCHAR(30);
FRAG VARCHAR(10);
CMD VARCHAR2(500);
CMD_ONLINE VARCHAR2(512);

EXAMINE_ONLY NUMBER;
NLOOP NUMBER;
LINES NUMBER;
FragRate NUMBER;
Iname VARCHAR(30);
DelRows INDEX_STATS.lf_rows%TYPE; --NUMBER
TabRows INDEX_STATS.lf_rows%TYPE; --NUMBER

BEGIN
DBMS_OUTPUT.PUT_LINE('-- INPUTSCHEMA MUSS!! GEQUOTED EINGEGEBEN WERDEN ');
USERNAM := &&INPUTSCHEMA;

EXAMINE_ONLY := 1;
EXAMINE_ONLY := &&NOWRITE;

/* 0 ==> FALSE; 1 ==> TRUE */

NLOOP := 0;

-- Hauptschleife über die INDIZES
FOR NIM_IND IN ( SELECT index_name, table_name FROM dba_indexes
WHERE OWNER = USERNAM AND Index_type = 'NORMAL'
AND TABLE_NAME IS NOT NULL
ORDER BY INDEX_NAME
)
LOOP
BEGIN -- cause excepzion

NIND_NAME := NIM_IND.index_name;
NTAB_NAME := NIM_IND.table_name;

NLOOP := NLOOP + 1;
DBMS_OUTPUT.PUT_LINE( NLOOP || ': INDEX ' || NIND_NAME);

IF 0=1 THEN -- DOCH NICHT COUNT ausführen
-- nicht zählen nur "ist mindestens eine Zeile da"
CMD := 'SELECT COUNT(*) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME || ' WHERE ROWNUM <= 1)';

EXECUTE IMMEDIATE CMD INTO LINES;

DBMS_OUTPUT.PUT_LINE(NTAB_NAME || ' ZEILEN : '||LINES);
ELSE
LINES := -1;
END IF;

IF LINES <> 0 THEN -- In Sonderfällen kann es nötig sein, trotzdem zu reorganiseren. Dann manuell!

--- Something to do?

CMD := 'ALTER INDEX "' || USERNAM || '"."' || NIND_NAME || '" rebuild ONLINE Nologging PARALLEL 4 ';

DBMS_OUTPUT.PUT_LINE(' REORG CMD: ' || CMD);
IF EXAMINE_ONLY = 0 THEN
DBMS_OUTPUT.PUT_LINE(' EXECUTING CMD ');
EXECUTE IMMEDIATE CMD;
COMMIT;
END IF;

END IF; -- LINES

EXCEPTION WHEN OTHERS THEN
BEGIN
IF SQLCODE = -942 THEN
-- http://www.dba-oracle.com/sf_ora_00942_table_or_view_does_not_exist.htm
DBMS_OUTPUT.PUT_LINE (' Misc.Problem Tabellenobjekt ' || NTAB_NAME );
END IF;
DBMS_OUTPUT.PUT_LINE ('Abgefangener Fehler : Errcode ' || SQLCODE);
GOTO END_LOOP;
END; -- EXCP

END;
<<END_LOOP>>
NULL;
END LOOP; -- NUM_TABLES;

END;
/
EXIT

NIMSOFT Tabellen RN_QOS_DATA_nnnn laufen voll, Archivierung versagte

Die Archivierung der NIMSOFT Daten auf dem zentralen Server stand. Der Server sammelt alle Daten aller Maschinen.

Allein eine Schleife mit einem count(*)  über die Datentabellen lief den ganzen Vormittag.   Zunächst die Ermittlung des Istzustandes.

Ich startete mit :

1
2
QUERYL := 'select COUNT(1) LINES FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA;

Verglichen wird gegen trunc(SYSDATE), nicht gegen SYSDATE, damit der evaluierte Ausdruck auch bei Wiederholung konstant bleibt, was bei dem puren SYSDATE nicht der Fall ist (Uhrzeitanteil würde sich ändern).  Könnte Cache-Benefit liefern. Theoretisch könnte man es auch als Bind-Variable hinschreiben.

Aber man kann die Performance noch erheblich steigern. Ähnlich wie bei einem „WHERE EXISTS“, wenn man nur wissen will ob etwas da ist, hilft hier statt dem Count auf die volle Ergebnismenge der Count auf die „limitierte Mindestmenge“. Da wir in erster Näherung resourcenschonend in 200000-er Blöcken Löschen wollten, konnte ich die Laufzeiten etwas verbessern mit

1
2
3
4
-- nicht direkt zählen nur "ist mindestens der zu löschende Block da"
QUERYL := 'SELECT COUNT(1) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM || ')';

So konnte man schon mal die Laufzeit der Ermittlung von „ganzem Vormittag“ auf einige Minuten drücken. 

Letzte Hürde:

Ungefähr drei Tabellen wehrten sich noch, mit Laufzeiten von bis zu sechs Minuten für eine de  Facto leere Tabelle ohne Daten. Woran kann das liegen?

BN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
DN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
HN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
RN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:53:01
BN_QOS_DATA_0042 NumRows: 0 TODELETE 0
29.11.2017 11:53:01

Struktur der Tabelle validiert. Tabelle reorganisiert (MOVE). Keine Auswirkung.

Index geprüft. Er ist nicht „unusable“.

Trotzdem war die Lösung ein „Alter Index REBUILD“.
Das neu Erzeugen des Index eliminierte das Performanceproblem.
Nachdem das Script noch um die Ausgabe der Deltatime pro Tabellenanalyse ergänzt wurden, fielen noch 2-3 weitere Kandidaten auf, deren Probleme genauso gelöst werden konnten. Eventuell hätte auch eine Suche nach  Indizes mit hohem Fragmentierungsgrad zum Ziel geführt, das könnte die Ursache gewesen sein. Habe daher auch schon bei Kunden für solche Fälle einen automatisierten Indexrebuild in der Wartung implementiert.

Ein Script löst dann die Probleme mit den aus dem Ruder gelaufenen Datenmengen ( > 900 Tage angefallen).

Folgende Zeile

RN_QOS_DATA_0001 NumRows: 36871127 TODELETE 200000

bedeutet, das in der Tabelle RN_QOS_DATA_0001 mindestens 200000 Zeilen zu löschen sind, die älter als 900 Tage sind.
In der nächsten Zeile haben wir ein Beispiel, wo die Tabelle nach der ersten Iteration schon clean ist, da nur 8815 Rows anfallen.

RN_QOS_DATA_0024 NumRows: 8815 TODELETE 8815

Kompletter Report:

Database = DB DELTA 900 -> DATUMSSCHWELLE 13-06-2015 READONLY1
BN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
DN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
HN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
RN_QOS_DATA_0001 NumRows: 36871127 TODELETE 200000
<===============
29.11.2017 15:29:37 delta t sec ,72
BN_QOS_DATA_0002 NumRows: 0 TODELETE 0
29.11.2017 15:29:37 delta t sec 0
DN_QOS_DATA_0002 NumRows: 0 TODELETE 0
29.11.2017 15:29:37 delta t sec 0

RN_QOS_DATA_0462 NumRows: 11992679 TODELETE 0
29.11.2017 15:29:54 delta t sec 0
CLONE_RN_QOS_DATA_0041 NumRows: TODELETE 0
29.11.2017 15:29:54 delta t sec 0
585 TABELLEN betrachtet
================> LÖSCHUNG IST ERFORDERLICH
Ein Löschbefehl:
DELETE FROM NMUSER.CLONE_RN_QOS_DATA_0041 WHERE TABLE_ID >= 0 AND SAMPLETIME < TRUNC(SYSDATE) – 900 AND ROWNUM <= 200000

Das Script das die Löschungen resourcenschonend durchführt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
DECLARE
/* Programmparameter = Konstanten zur Steuerung des Laufzeitverhaltens */
/* Eine Iteration dauert so knapp über 30 min , 4 -> ~2h */
MAXITER CONSTANT NUMBER := 4; zum Limitieren der Laufzeit nutzen
MAXROWNUM CONSTANT NUMBER := 200000; -- adaptieren ; jetzt ca 30 Minuten/iter
DELTA CONSTANT NUMBER := 900; -- DELTA Tage zurück!
USERNAM CONSTANT VARCHAR2(6) := 'NMUSER'; -- der Tableowner

CURSOR NIM_TABLES IS
SELECT table_name, num_rows
FROM dba_tables
WHERE OWNER = USERNAM
AND table_name LIKE '%N_QOS_DATA_%'
ORDER BY SUBSTR(table_name,11),SUBSTR(table_name,1,1);

QUERYL VARCHAR2(500);
D_DML VARCHAR2(500);
TOKEN VARCHAR(32);
LINES NUMBER;
TODELETE NUMBER; -- Löschung erforderlich
GLOBTODELETE NUMBER; -- GLOBALES TODELETE
DELDONE NUMBER; -- hat gelöscht?
NITER NUMBER; -- Anzahl Iterationen
TBD NUMBER; -- noch was zu tun?
RERUN_HINT NUMBER;
EXAMINE_ONLY NUMBER; -- READONLY J/N
POSSIBLE_LIMIT NUMBER;
NLOOP NUMBER;
BTABLE BOOLEAN;
V_DBNAME VARCHAR2(132);
V_DBID NUMBER;
runtime DATE;
runseconds NUMBER;
timesnap DATE;

N_ROWS DBA_TABLES.NUM_ROWS%TYPE;
NTAB_NAME DBA_TABLES.TABLE_NAME%TYPE;

DLIMIT DATE;

 

BEGIN
EXAMINE_ONLY := &&NODELETE;
--EXAMINE_ONLY := 1;
/* 0 ==> FALSE; 1 ==> TRUE */
DELDONE := 0; TBD := 0; RERUN_HINT := 0; POSSIBLE_LIMIT := 1;
DLIMIT := SYSDATE-DELTA;
GLOBTODELETE := 0;
SELECT NAME,DBID INTO V_DBNAME,V_DBID FROM V$DATABASE;

DBMS_OUTPUT.PUT_LINE('Database = '||V_DBNAME || ' DELTA ' || DELTA ||
' -> DATUMSSCHWELLE ' || TO_CHAR(DLIMIT, 'DD-MM-YYYY ') || ' READONLY' || EXAMINE_ONLY );

NLOOP := 0;
-- Hauptschleife über die NIMTABLES
OPEN NIM_TABLES;
LOOP
FETCH NIM_TABLES INTO
NTAB_NAME, N_ROWS;
EXIT WHEN NIM_TABLES%NOTFOUND;

NLOOP := NLOOP + 1;
LINES := 0;

BTABLE := SUBSTR(NTAB_NAME,1,1) = 'B';

IF NOT BTABLE THEN
TOKEN := 'SAMPLETIME';
ELSE
TOKEN := 'STOPTIME';
END IF;

timesnap := SYSDATE;

-- nicht zählen nur "ist mindestens der zu löschende Block da"
QUERYL := 'SELECT COUNT(1) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM || ')'; -- DBMS_OUTPUT.PUT_LINE(QUERYL); EXECUTE IMMEDIATE QUERYL INTO LINES; DBMS_OUTPUT.PUT_LINE(NTAB_NAME || ' NumRows: ' || N_ROWS || ' TODELETE '||LINES); IF LINES > 0 THEN
TODELETE := 1;
GLOBTODELETE := 1;
DBMS_OUTPUT.PUT_LINE(' <=============== '); ELSE TODELETE := 0; END IF; IF LINES >= MAXROWNUM THEN
RERUN_HINT := 1;
END IF;

DBMS_OUTPUT.PUT( TO_CHAR(SYSDATE, 'DD.MM.YYYY hh24:mi:ss') );
runseconds := 24 * 2600 * ( SYSDATE - timesnap);
DBMS_OUTPUT.put( ' delta t sec '|| TO_CHAR( runseconds , '9999.9') );
IF runseconds > 1 THEN
DBMS_OUTPUT.PUT_LINE ( ' MUCH PERFORMED ***** ');
ELSE
DBMS_OUTPUT.PUT_LINE ( ' ');
END IF;
timesnap := SYSDATE;

IF TODELETE > 0 THEN
-- Löschbefehl aufbereiten
-- Loop über Löschblöcke Designentscheidung. Inner Loop Block oder Table?
D_DML := 'DELETE FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM;

IF EXAMINE_ONLY <> 0 THEN
DBMS_OUTPUT.PUT_LINE(' READONLY ');
--NULL;
ELSE

NITER := 0;
-- INNER LOOP - ITERATE Nx MAXROWNUM
WHILE TODELETE > 0 AND NITER < MAXITER LOOP IF NITER = 0 THEN DBMS_OUTPUT.PUT(' EXEC: ' ); DBMS_OUTPUT.PUT_LINE(' SHOULD DELETE CMD: ' || D_DML); END IF; EXECUTE IMMEDIATE D_DML; COMMIT; DELDONE := 1; DBMS_OUTPUT.PUT_LINE('Iteration ' || NITER || ' Es wurden '|| LINES || ' Zeilen gelöscht '); EXECUTE IMMEDIATE QUERYL INTO LINES; IF LINES > 0 THEN
TODELETE := 1;
ELSE
TODELETE := 0;
END IF;

IF LINES >= MAXROWNUM THEN
RERUN_HINT := 1;
ELSE
RERUN_HINT := 0;
END IF;

NITER := NITER +1;

END LOOP;
NULL;
END IF;

END IF; -- TODELETE

-- LIMIT FÜR TESTLAUF
--EXIT WHEN NLOOP >= 5;

END LOOP; -- NUM_TABLES;
CLOSE NIM_TABLES;

DBMS_OUTPUT.PUT_LINE(NLOOP || ' TABELLEN betrachtet ');

IF GLOBTODELETE > 0 THEN

IF DELDONE > 0 THEN
DBMS_OUTPUT.PUT_LINE(' Es wurde gelöscht ');
IF RERUN_HINT > 0 THEN
DBMS_OUTPUT.PUT_LINE(' Bitte wiederhole Aufruf, limitierter Lauf wegen Resourcenlimit ');
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE(' ================> LÖSCHUNG IST ERFORDERLICH ');
DBMS_OUTPUT.PUT_LINE('Letzter exemplarischer Befehl: ');
DBMS_OUTPUT.PUT_LINE(D_DML);
END IF;

ELSE
DBMS_OUTPUT.PUT_LINE('Es war keine Löschung erforderlich ');
END IF;

EXCEPTION WHEN OTHERS THEN
IF SQLCODE = -12801 THEN
DBMS_OUTPUT.PUT_LINE (' TEMP Segment zu klein? Empfohlen ist mind. 600MB');
END IF;
DBMS_OUTPUT.PUT_LINE ('Abbruch -> Errcode ' || SQLCODE);
END;

Es folgt dann noch ein wesentlicher Schritt. Wie auch bei der SYSAUX-Bereinigung ist ein entscheidender Schritt der Reorg der indizes, die über die Jahre einen Fragmentierungsgrad von bis zu 85% erreicht haben.
Dies verbraucht viel Platz sowie Performance, daher ist nach dem Löschen der Rebuild der Indizes nötig. Glücklicherweise gibt es in der Enterprise Version einen Qualifier REBUILD ONLINE, damit ist im Rebuild die Tabelle nicht gelockt, kann also während des Betriebs erfolgen. Der Rebuild der ersten drei Indizes bracht schon 7GB Freespace.

produktives Script Online Rebuild :

(wird folgen) 

fail2ban macht den Server sicherer – speziell ssh und auch dovecot auf DEBIAN STRETCH

Fail2ban dient dazu, ständige Einlogversuche von fremden Rechnern auf den ssh-port zu unterbinden. Auch andere Ports können damit überwacht werden.

Nachdem sogar Fremdlogins auf den imap-port für existierende User auftauchten, stieg die empfundene Handlungsnotwendigkeit.

Beispiel: (Auszug aus /var/log/auth.log)

1
2
3
4
5
6
7
8
9
10
Dec 8 07:31:25 myserver sshd[9656]: Invalid user si from 113.120.16.165 port 51268
Dec 8 07:31:25 myserver sshd[9654]: Invalid user si from 113.120.16.165 port 51264
Dec 8 07:31:25 myserver sshd[9656]: input_userauth_request: invalid user si [preauth]
Dec 8 07:31:25 myserver sshd[9654]: input_userauth_request: invalid user si [preauth]
Dec 8 07:31:26 myserver sshd[9656]: pam_unix(sshd:auth): check pass; user unknown
Dec 8 07:31:26 myserver sshd[9656]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=113.120.16.165
Dec 8 07:31:26 myserver sshd[9654]: pam_unix(sshd:auth): check pass; user unknown
Dec 8 07:31:26 myserver sshd[9654]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=113.120.16.165
Dec 8 07:31:28 myserver sshd[9656]: Failed password for invalid user si from 113.120.16.165 port 51268 ssh2
Dec 8 07:31:28 myserver sshd[9654]: Failed password for invalid user si from 113.120.16.165 port 51264 ssh2

IP 113.120.16.165 startet einen Angriff und versucht sich über ssh einzuloggen.
Das bewirkt folgendes:

fail2ban log:

1
2
3
4
5
2017-12-08 07:31:25,911 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:25,912 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,285 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,288 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,828 fail2ban.actions [14863]: NOTICE [sshd] Ban 113.120.16.165

Die IP wird in der Firewall eingetragen und gebannt. Man kontrolliert das etwa mit:

tail -40 /var/log/fail2ban.log

 

Weiter kann man es in der Firewall kontrollieren: Ein „iptables-save“ liefert unter anderem:

-A f2b-sshd -s 113.120.16.165/32 -j REJECT –reject-with icmp-port-unreachable

 

Zu konfigurieren ist unter anderem in der jail.conf

  • findtime = 600 (Zeitfenster für den Zähler Fehlversuche pro IP)
  • maxRetry = 3 (Der Ban erfolgt nach 3 Fehlversuchen)
  • banTime = 3600 (Die IP ist für eine Stunde gebannt)

Der Teil für den sshd war trivial. Problematischer war der Part für den dovecot.

Es fiel auf, dass auf Versuche fremder IPs sich als Mailuser einzuloggen, nichts bewirkten.
Kurzer Check der Sachlage ergab, DEBIAN 9 trägt die Loginversuche aus IMAP (dovecot) in der auth.log ein:

Dec 8 07:07:31 myserver:auth: pam_unix(dovecot:auth): check pass; user unknown
Dec 8 07:07:31 myserver auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=sfsb rhost=130.204.239.232

fail2ban ist aber auf die mail.warn konfiguriert.

Am schnellsten korrigiert man das durch Überschreiben der entsprechenden Konfiguration in der Konfig (jail.local)

# dovecot defaults to logging to the mail syslog facility
# but can be set by syslog_facility in the dovecot configuration.
[dovecot]
# enabled = true
port = pop3,pop3s,imap,imaps,submission,465,993,sieve
#logpath = %(dovecot_log)s
logpath = /var/log/auth.log
backend = %(dovecot_backend)s

Mit dem harten Überschreiben des logpath in der Section für dovecot ist das Problem gelöst:

2017-12-08 10:34:08,015 fail2ban.filter [28202]: INFO [dovecot] Found 218.23.49.154

 


Etwas allgemeiner Ansatz wäre, in der paths-common.conf (in /etc/fail2ban/) folgendes zu überschreiben.

dovecot_log = %(syslog_mail_warn)s

Seiteneffekte sind jedoch unklar, man findet da folgendes vor:

# There is no sensible generic defaults for syslog log targets, thus
# leaving them empty here so that no errors while parsing/interpolating configs
syslog_mail_warn =
syslog_daemon =
syslog_ftp =
syslog_local0 =

Damit würde wohl:
dovecot_log = /var/log/auth.log
auch das Problem lösen.

Weitere Informationen über die Konfigurationsmöglichkeiten von fail2ban-server erhält man mit „man fail2ban-client“.

Number of processes exceeded: zeitliche Entwicklung als Kurzübersicht

Auf einer 12c Datenbank zeigte sich ein ORA-0020

ORA-00020: maximum number of processes (300) exceeded

Bevor der Parameter erhöht wird mit

ALTER SESSION SET PROCESSES=350 scope=BOTH;

lohnt es sich zu schauen, wie denn die Entwicklung der letzten Tage (max des Values in der History) war.

Folgender SQL-Befehl leistet das:

1
2
3
4
5
6
7
8
9
10
-- Maximum per day:
SELECT instance_number inst, TO_CHAR( begin_time, 'YYYY-MM-DD') DATUM , MAX(processes) FROM
(SELECT instance_number, ROUND(maxval/100 * gv$parameter.VALUE) processes, begin_time
FROM dba_hist_sysmetric_summary
join gv$parameter
ON dba_hist_sysmetric_summary.instance_number = gv$parameter.inst_id
WHERE gv$<a href="http://parameter.name">parameter.name</a> = 'processes'
AND metric_name = 'Process Limit %')
GROUP BY instance_number,TO_CHAR( begin_time, 'YYYY-MM-DD')
ORDER BY DATUM DESC;

Ausgabe:

INST DATUM MAX(PROCESSES)
 ---------- ---------- --------------
 1 2017-11-27 299
 1 2017-11-26 265
 1 2017-11-25 268
 1 2017-11-24 265
 1 2017-11-23 218
 1 2017-11-22 219
 1 2017-11-21 218
 1 2017-11-20 137
 1 2017-11-19 137
 1 2017-11-18 134

Nun kann man anhand der Entwicklung entscheiden, ob man auf jeden Fall den INIT.ORA Parameter überhöht, oder ob man es als einzelen Außreisser wertet. Selbst dann müsste man entscheiden ob man nicht dennoch erhöht.
Jedoch hier spricht die stetig steigende Resourcenauslastung, wenn keine weitere Ursachenforschung sinnvoll erscheint, für die pragmatische Erhöhung zu sprechen.

ORACLE: Passwort = Username, unsichere Passwörter ermitteln

 unsicheres Passwort, identisch mit Username oder „meistgenutzt“ (auf Liste)
Sicherheitslücke: User mit leicht zu erratenden Passwort

Ein weiterer Aspekt, der es potentiellen Angreifern leichter macht ist der Fall, dass ein User sein Passwort als Usernamen gewählt hat.

Wir stellen also ein einfaches Script vor, das die User ermittelt die in diese  Sicherheitsklasse fallen.

Wir gehen von der Annahme aus, der User hätte Username = Passwort.

Schritte zu unserer Lösung:
  1. Loopen über alle DB-User
  2. hashen unseres angenommen Passworts nach der Standardmethode
  3. ziehen des gespeicherten Hashs
  4. compare. Bei Treffer, Listung des Users

Um einen Testfall zu haben, machen wir folgendes, ausgehend von User SCOTT, oder einem neu anzulegenden User TEST, um einen Testfall für das Script zu haben:

ALTER USER SCOTT IDENTIFIED BY SCOTT;

 

Wir lassen unser Script laufen:

SCOTT is UNSAFE PWD guessed IS SCO******
TEST is UNSAFE PWD guessed IS TES******

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Oracle Passwörter sind jedoch nicht mehr Case-Insensitiv.
Was ist, wenn des Users Passwort „Scott“ lautet? Es sind immerhin nur wenige Versuche für den potentiellen Angreifer, die KOmbinationen SCOTT, Scott sowie scott durchzuprobieren. Wir testen das aus:

ALTER USER SCOTT IDENTIFIED BY Scott;

Ergebnis:

SCOTT is UNSAFE PWD guessed IS Sco******
TEST is UNSAFE PWD guessed IS TES******

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Unser Script findet das auch! Natürlich haben wir das schon bedacht, obige Steps 2-4 erfolgen jeweils für die Varianten „angenommenes Passwort Username“ in den beschriebenen drei Varianten.

  1. Loopen über alle DB-User
  2. Erstelle Passwort wie Username in Lowercase ( jump to 5)
  3. Erstelle Passwort wie Username in Uppercase  ( jump to 5)
  4. Erstelle Passwort wie Username in Mixedcase (Capitals) ( jump to 5)
  5. hashen unseres angenommen Passworts nach der Standardmethode
  6. ziehen des gespeicherten Hashs
  7. compare. Bei Treffer, Listung des Users.
  8. jump back – next step

Wie sieht das script aus? Hier haben wir es:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
SET serveroutput ON size 1000000

DECLARE
unsecure BOOLEAN;
DUMMY VARCHAR2(80);
c_username VARCHAR2(256);
chk_token VARCHAR2(256);
dbname v$database.name%TYPE;

lv_pwd_raw RAW(128);
lv_enc_raw RAW(2048);
LV_HASH_FOUND VARCHAR2(300);

CURSOR c_main(cp_user IN VARCHAR2)
IS
SELECT SUBSTR(spare4,3,40) hash,
SUBSTR(spare4,43,20) salt,
spare4
FROM sys.USER$
WHERE name=cp_user;
lv_user c_main%ROWTYPE;

CURSOR cu_user
IS
SELECT username
FROM dba_users
WHERE ACCOUNT_STATUS LIKE 'OPEN%';

FUNCTION COMPARE_PW_HASH(luser IN VARCHAR2, lpw IN VARCHAR2)
RETURN NUMBER IS

BEGIN
OPEN c_main(UPPER(luser));
FETCH c_main INTO lv_user;
CLOSE C_MAIN;
lv_pwd_raw := UTL_RAW.cast_to_raw(lpw)||HEXTORAW(lv_user.salt);
lv_enc_raw := sys.dbms_crypto.hash(lv_pwd_raw, 3);
lv_hash_found:=UTL_RAW.cast_to_varchar2(lv_enc_raw);

IF LV_ENC_RAW = LV_USER.hash THEN
-- DBMS_OUTPUT.PUT_LINE('PWD found for User ' || luser );
RETURN 1;
ELSE
-- dbms_output.put_line('PWD not found for Safe User ' || luser);
RETURN 0;
END IF;
END;

BEGIN
DBMS_OUTPUT.ENABLE(10000000);

SELECT NAME INTO dbname FROM v$database;
--DBMS_OUTPUT.PUT_LINE('Start Checking DB ' || dbname);
--DBMS_OUTPUT.PUT_LINE('');

OPEN cu_user;
LOOP
FETCH cu_user INTO c_username;
EXIT WHEN cu_user%NOTFOUND;

unsecure := FALSE;

IF COMPARE_PW_HASH ( c_username , c_username ) > 0 THEN
unsecure := TRUE;
chk_token := c_username;
ELSIF COMPARE_PW_HASH ( c_username , UPPER(c_username) ) > 0 THEN
unsecure := TRUE;
chk_token := UPPER ( c_username) ;
ELSIF COMPARE_PW_HASH ( c_username , UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)))
) > 0 THEN
unsecure := TRUE;
chk_token := UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)));
ELSIF COMPARE_PW_HASH ( c_username , '123456' ) > 0 THEN
unsecure := TRUE;
chk_token := '123456';
END IF;

IF unsecure THEN
DBMS_OUTPUT.PUT_LINE( c_username || ' is UNSAFE PWD guessed IS '
|| SUBSTR(chk_token,1,3) || '******' );
ELSE
--DBMS_OUTPUT.PUT_LINE(c_username );
NULL;
END IF;
END LOOP;
CLOSE cu_user;
--
END;
Prüfung auf beliebte, damit unsichere Passwörter

 

Jetzt verfeinern wir das Script noch um einen kleinen Aspekt. Erstaunlicherweise gibt es tatsächlich „beliebte Passwörter“.
Hier Artikel zu dem Thema:

https://www.com-magazin.de/news/sicherheit/25-unsichersten-passwoerter-2015-1072804.html

http://www.maclife.de/news/diese-passwoerter-sollten-keinen-fall-verwenden-10086942.html

Passwort Hitliste weltweit 2015:
  1. 123456
  2. password
  3. 12345678
  4. qwerty
  5. …. (siehe Link)

Das heisst, Passwörter wie „123456“ sollten wir auch nicht verwenden.
Dieses Passwort verwenden laut dieser Untersuchungen signifikant viele Personen, daher wären es für den potentiellen Angreifer auch nur einige wenige Versuche die ersten 20% Wahrscheinlichkeit des benutzten Passwortes abzuchecken. Daher nehmen wir diese in eine erweitere Variante des Scriptes auf. Die eingebaute Liste der Passwörter ist natürlich nachzupflegen. In der Luxusvariante läge sie in einer Tabelle, in der mindestens jährlich die neuesten Passworthits eingepflegt werden, uns reicht hier für das Prinzip jedoch eine fest verdrahtete Liste.

Wir testen das:

ALTER USER SCOTT IDENTIFIED BY 123456;

 

Ausgabe des neuen Scriptes:

SCOTT is UNSAFE PWSTYLE IS 4 **
TEST is UNSAFE PWSTYLE IS 1 **

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Was hat sich geändert?  Nun, es wird nicht mehr das Passwort , auch nicht dessen Anfang, im Klartext wiederholt, auch der Admin muss das gar nicht wissen. Es reicht die Kategorisierung in eine Fehlerklasse.

SCOTT fällt jetzt in die Fehlerklasse 4. Ein zu implementierender Automatismus könnte ihn also anmailen, oder der Admin ansprechen und darauf hinweisen: „Lieber User SCOTT, du hast ein unsicheres Passwort. Dieses steht auf der Liste der weltweit am meisten genutzten Passwörter. Bitte denke dir aus Sicherheitsgründen, zu unsere aller Beruhigung, ein neues Passwort aus.„.

Kategorien der Fehlerklassen:

 

  1. Passwort wie username (in Lowercase )
  2. Passwort wie USERNAME  (in Uppercase )
  3. Passwort wie Username  (in Mixedcase (Capitals) )
  4. befindet sich in Hitliste der beliebtesten Passwörter

Hier noch zu guter Letzt das erweiterte Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
SET serveroutput ON size 1000000

DECLARE
TYPE string_list IS VARRAY(40) OF VARCHAR(16);

pw_list STRING_LIST := string_list(
'123456','password','12345678','qwerty','12345','123456789','football',
'1234','1234567','baseball','welcome','1234567890','abc123','111111',
'1qaz2wsx','dragon','master','monkey','letmein','login','princess',
'qwertyuiop', 'solo', 'passw0rd', 'starwars', 'hallo', 'passwort',
'hallo123', 'schalke04', 'passwort1', 'qwertz', 'schatz', 'hallo1',
'fussball', NULL, NULL, NULL,NULL,NULL,NULL ); -- hier Erweiterungen eintragen

pwstyle PLS_INTEGER;
DUMMY VARCHAR2(80);
c_username VARCHAR2(256);
dbname v$database.name%TYPE;

lv_pwd_raw RAW(128);
lv_enc_raw RAW(2048);
LV_HASH_FOUND VARCHAR2(300);

CURSOR c_main(cp_user IN VARCHAR2)
IS
SELECT SUBSTR(spare4,3,40) hash,
SUBSTR(spare4,43,20) salt,
spare4
FROM sys.USER$
WHERE name=cp_user;
lv_user c_main%ROWTYPE;

CURSOR cu_user
IS
SELECT username
FROM dba_users
WHERE ACCOUNT_STATUS LIKE 'OPEN%';

FUNCTION COMPARE_PW_HASH(luser IN VARCHAR2, lpw IN VARCHAR2)
RETURN NUMBER IS

BEGIN
OPEN c_main(UPPER(luser));
FETCH c_main INTO lv_user;
CLOSE C_MAIN;
lv_pwd_raw := UTL_RAW.cast_to_raw(lpw)||HEXTORAW(lv_user.salt);
lv_enc_raw := sys.dbms_crypto.hash(lv_pwd_raw, 3);
lv_hash_found:=UTL_RAW.cast_to_varchar2(lv_enc_raw);

IF LV_ENC_RAW = LV_USER.hash THEN
-- DBMS_OUTPUT.PUT_LINE('PWD found for User ' || luser );
RETURN 1;
ELSE
RETURN 0;
END IF;
END;

BEGIN
DBMS_OUTPUT.ENABLE(10000000);

SELECT NAME INTO dbname FROM v$database;

OPEN cu_user;
LOOP
FETCH cu_user INTO c_username;
EXIT WHEN cu_user%NOTFOUND;

pwstyle := 0;

IF COMPARE_PW_HASH ( c_username , c_username ) > 0 THEN
pwstyle := 1; -- PW = username
ELSIF COMPARE_PW_HASH ( c_username , UPPER(c_username) ) > 0 THEN
pwstyle := 2; -- PW = UPPER(USERNAME)
ELSIF COMPARE_PW_HASH ( c_username , UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)))
) > 0 THEN
pwstyle := 3; -- PW = Username
ELSE
FOR i IN 1..36 LOOP
IF COMPARE_PW_HASH ( c_username , pw_list(i) ) > 0 THEN
pwstyle := i+3; -- PW ist PW(i) aus Liste der meistgenutzten
EXIT;
END IF;
END LOOP;
END IF;

IF pwstyle > 0 THEN
DBMS_OUTPUT.PUT_LINE( c_username || ' is UNSAFE PWSTYLE IS ' || PWSTYLE || ' **' );
END IF;
END LOOP;
CLOSE cu_user;
--
END;
/
Wissen zum Thema:

 

https://www.experts-exchange.com/articles/855/How-Oracle-Stores-Passwords.html

ORACLE DB, finde User mit Default Passwort

Ein kurzer Augenmerk auf eine kleine Feinheit zum Thema Sicherheit:

Selbst aktuell inaktive Standarduser mit Default Passwort sind unschön, aktiviert man diese und vergisst gleichzeitig das Passwort zu ändern, präsentiert man einen Account den ein Angreifer mit Grundwissen nutzen kann. Daher ist es besser bei Routinechecks diesen Punkt prüfen zu können.

Oracle bietet dafür die VIEW mit sprechendem Namen:  DBA_USERS_WITH_DEFPWD

Nutzen wir sie mit:

select * from dba_users_with_defpwd;

 

Die Ausgabe kann lauten:

USERNAME
——————————
APPQOSSYS
XDB
MDSYS
EXFSYS
SI_INFORMTN_SCHEMA
DIP
ORACLE_OCM
ORDSYS
WMSYS
ORDDATA
CTXSYS
ORDPLUGINS
OUTLN
SCOTT

14 Zeilen gewählt.

User SCOTT taucht in der Liste auf, da er das Standardpasswort TIGER hat.

Natürlich können wir das schnell korrigieren:

ALTER USER SCOTT IDENTIFIED BY SCOTT;

Jetzt haben wir das Problem gelöst, der User hat kein Defaultpasswort mehr und taucht auch nicht mehr auf, wenn wir den Report neu ziehen.

Ganz genau betrachtet haben wir aber ein neues Problem generiert.
Es ist ebenfalls unsicher einem User das gleiche Passwort wie der Username zu geben. Zu den Detailaspekten dieses Problems gibt es folgend einen weiteren Beitrag.