Know Your Font: update on SQL Developer oddity

A while back I posted a note about an oddity I ran into using SQL Developer on Windows (see here for details). As I couldn’t leave the issue alone I eventually found the root cause with a little help from my friends (fellow developers): It’s the font “DialogInput” SQL Developer uses by default. As I learned, “DialogInput” is a so-called logical font (read about that here). Apparently, these logical fonts are a composition of multiple physical fonts depending on what’s actually installed on your system and hence, differ on my Linux and Windows systems. This little Java program prints out the font composition:

import java.awt.Font;
import java.lang.reflect.Method;
import java.util.Locale;

import sun.font.CompositeFont;
import sun.font.Font2D;
import sun.font.PhysicalFont;

public class Test {
    public static void main(String... args)
    throws Exception
    {
        Font font = new Font("DialogInput", Font.PLAIN, 12);
        describeFont(font);
    }

    private static void describeFont(Font font)
    throws Exception
    {
        Method method = font.getClass().getDeclaredMethod("getFont2D");
        method.setAccessible(true);
        Font2D f = (Font2D)method.invoke(font);

        describeFont2D(f);
    }

    private static void describeFont2D(Font2D font)
    {
        if (font instanceof CompositeFont)
        {
            System.out.println("Font '" + font.getFontName(Locale.getDefault()) + "' is composed of:");

            CompositeFont cf = (CompositeFont)font;
            for (int i = 0; i < cf.getNumSlots(); i++)
            {
                PhysicalFont pf = cf.getSlotFont(i);
                describeFont2D(pf);
            }
        }
        else
            System.out.println("-> " + font);
    }
}

Output from my Linux system:

Font 'DialogInput.plain' is composed of:
-> ** TrueType Font: Family=DejaVu Sans Mono Name=DejaVu Sans Mono style=0 fileName=/usr/share/fonts/dejavu/DejaVuSansMono.ttf
-> ** TrueType Font: Family=DejaVu Sans Mono Name=DejaVu Sans Mono Bold style=1 fileName=/usr/share/fonts/dejavu/DejaVuSansMono-Bold.ttf
-> ** Type1 Font: Family=Nimbus Mono L Name=Nimbus Mono L Regular style=0 fileName=/usr/share/fonts/default/Type1/n022003l.pfb
-> ** TrueType Font: Family=VL Gothic Name=VL Gothic Regular style=0 fileName=/usr/share/fonts/vlgothic/VL-Gothic-Regular.ttf
-> ** TrueType Font: Family=NanumGothic Name=NanumGothic style=0 fileName=/usr/share/fonts/nhn-nanum/NanumGothic.ttf
-> ** TrueType Font: Family=Lohit Bengali Name=Lohit Bengali style=0 fileName=/usr/share/fonts/lohit-bengali/Lohit-Bengali.ttf
-> ** TrueType Font: Family=Lohit Gujarati Name=Lohit Gujarati style=0 fileName=/usr/share/fonts/lohit-gujarati/Lohit-Gujarati.ttf
-> ** TrueType Font: Family=Lohit Punjabi Name=Lohit Punjabi style=0 fileName=/usr/share/fonts/lohit-punjabi/Lohit-Punjabi.ttf
-> ** TrueType Font: Family=Lohit Tamil Name=Lohit Tamil style=0 fileName=/usr/share/fonts/lohit-tamil/Lohit-Tamil.ttf
-> ** TrueType Font: Family=Meera Name=Meera style=0 fileName=/usr/share/fonts/smc/Meera.ttf
-> ** TrueType Font: Family=Lohit Kannada Name=Lohit Kannada style=0 fileName=/usr/share/fonts/lohit-kannada/Lohit-Kannada.ttf
-> ** TrueType Font: Family=Lohit Telugu Name=Lohit Telugu style=0 fileName=/usr/share/fonts/lohit-telugu/Lohit-Telugu.ttf
-> ** TrueType Font: Family=Lohit Oriya Name=Lohit Oriya style=0 fileName=/usr/share/fonts/lohit-oriya/Lohit-Oriya.ttf
-> ** TrueType Font: Family=LKLUG Name=LKLUG style=0 fileName=/usr/share/fonts/lklug/lklug.ttf
-> ** TrueType Font: Family=FreeMono Name=FreeMono style=0 fileName=/usr/share/fonts/gnu-free/FreeMono.ttf
-> ** TrueType Font: Family=FreeSans Name=FreeSans style=0 fileName=/usr/share/fonts/gnu-free/FreeSans.ttf
-> ** TrueType Font: Family=FreeSerif Name=FreeSerif style=0 fileName=/usr/share/fonts/gnu-free/FreeSerif.ttf
-> ** TrueType Font: Family=OpenSymbol Name=OpenSymbol style=0 fileName=/usr/share/fonts/opensymbol/opens___.ttf
-> ** TrueType Font: Family=PakType Naqsh Name=PakType Naqsh style=0 fileName=/usr/share/fonts/paktype-naqsh/PakType_Naqsh.ttf
-> ** TrueType Font: Family=Khmer OS Name=Khmer OS style=0 fileName=/usr/share/fonts/khmeros/KhmerOS.ttf
-> ** TrueType Font: Family=Padauk Name=Padauk style=0 fileName=/usr/share/fonts/sil-padauk/Padauk.ttf
-> ** TrueType Font: Family=Lohit Devanagari Name=Lohit Devanagari style=0 fileName=/usr/share/fonts/lohit-devanagari/Lohit-Devanagari.ttf
-> ** TrueType Font: Family=DejaVu Sans Name=DejaVu Sans style=0 fileName=/usr/share/fonts/dejavu/DejaVuSans.ttf
-> ** TrueType Font: Family=DejaVu Serif Name=DejaVu Serif style=0 fileName=/usr/share/fonts/dejavu/DejaVuSerif.ttf
-> ** TrueType Font: Family=Nuosu SIL Name=Nuosu SIL style=0 fileName=/usr/share/fonts/sil-nuosu/NuosuSIL.ttf
-> ** TrueType Font: Family=PT Sans Name=PT Sans style=0 fileName=/usr/share/fonts/paratype-pt-sans/PTS55F.ttf
-> ** TrueType Font: Family=Abyssinica SIL Name=Abyssinica SIL style=0 fileName=/usr/share/fonts/sil-abyssinica/AbyssinicaSIL-R.ttf
-> ** TrueType Font: Family=Carlito Name=Carlito style=0 fileName=/usr/share/fonts/google-crosextra-carlito/Carlito-Regular.ttf
-> ** TrueType Font: Family=Liberation Sans Name=Liberation Sans style=0 fileName=/usr/share/fonts/liberation/LiberationSans-Regular.ttf
-> ** TrueType Font: Family=Liberation Serif Name=Liberation Serif style=0 fileName=/usr/share/fonts/liberation/LiberationSerif-Regular.ttf
-> ** TrueType Font: Family=Noto Sans Mandaic Name=Noto Sans Mandaic style=0 fileName=/usr/share/fonts/google-noto/NotoSansMandaic-Regular.ttf
-> ** TrueType Font: Family=Noto Sans Meetei Mayek Name=Noto Sans Meetei Mayek style=0 fileName=/usr/share/fonts/google-noto/NotoSansMeeteiMayek-Regular.ttf
-> ** TrueType Font: Family=Noto Sans Tagalog Name=Noto Sans Tagalog style=0 fileName=/usr/share/fonts/google-noto/NotoSansTagalog-Regular.ttf
-> ** TrueType Font: Family=Noto Sans Tai Tham Name=Noto Sans Tai Tham style=0 fileName=/usr/share/fonts/google-noto/NotoSansTaiTham-Regular.ttf
-> ** TrueType Font: Family=Noto Sans Tai Viet Name=Noto Sans Tai Viet style=0 fileName=/usr/share/fonts/google-noto/NotoSansTaiViet-Regular.ttf
-> ** TrueType Font: Family=Waree Name=Waree style=0 fileName=/usr/share/fonts/thai-scalable/Waree.ttf
-> ** TrueType Font: Family=Mingzat Name=Mingzat style=0 fileName=/usr/share/fonts/sil-mingzat/Mingzat-R.ttf
-> ** TrueType Font: Family=Jomolhari Name=Jomolhari style=0 fileName=/usr/share/fonts/jomolhari/Jomolhari-alpha3c-0605331.ttf
-> ** TrueType Font: Family=WenQuanYi Zen Hei Name=WenQuanYi Zen Hei style=0 fileName=/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc
-> ** TrueType Font: Family=WenQuanYi Zen Hei Name=WenQuanYi Zen Hei style=0 fileName=/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc
-> ** TrueType Font: Family=AR PL UMing CN Name=AR PL UMing CN style=0 fileName=/usr/share/fonts/cjkuni-uming/uming.ttc
-> ** TrueType Font: Family=AR PL UMing CN Name=AR PL UMing CN style=0 fileName=/usr/share/fonts/cjkuni-uming/uming.ttc
-> ** TrueType Font: Family=AR PL UMing CN Name=AR PL UMing CN style=0 fileName=/usr/share/fonts/cjkuni-uming/uming.ttc

Output from my Windows system:

FFont 'DialogInput.plain' is composed of:
-> ** TrueType Font: Family=Courier New Name=Courier New style=0 fileName=C:\Windows\Fonts\COUR.TTF
-> ** TrueType Font: Family=Wingdings Name=Wingdings style=0 fileName=C:\Windows\Fonts\WINGDING.TTF
-> ** TrueType Font: Family=Symbol Name=Symbol style=0 fileName=C:\Windows\Fonts\SYMBOL.TTF
-> ** TrueType Font: Family=Lucida Sans Name=Lucida Sans Regular style=0 fileName=C:\Program Files\Java\jdk1.7.0_45\jre\lib\fonts\LucidaSansRegular.ttf
-> ** TrueType Font: Family=MingLiU Name=MingLiU style=0 fileName=C:\Windows\Fonts\MINGLIU.TTC
-> ** TrueType Font: Family=Lucida Sans Name=Lucida Sans Regular style=0 fileName=C:\Program Files\Java\jdk1.7.0_45\jre\lib\fonts\LucidaSansRegular.ttf
-> ** TrueType Font: Family=SimSun Name=SimSun style=0 fileName=C:\Windows\Fonts\SIMSUN.TTC
-> ** TrueType Font: Family=Lucida Sans Name=Lucida Sans Regular style=0 fileName=C:\Program Files\Java\jdk1.7.0_45\jre\lib\fonts\LucidaSansRegular.ttf
-> ** TrueType Font: Family=MS Gothic Name=MS Gothic style=0 fileName=C:\Windows\Fonts\MSGOTHIC.TTC
-> ** TrueType Font: Family=Gulim Name=Gulim style=0 fileName=C:\Windows\Fonts\gulim.TTC
-> ** TrueType Font: Family=MingLiU-ExtB Name=MingLiU-ExtB style=0 fileName=C:\Windows\Fonts\MINGLIUB.TTC
-> ** TrueType Font: Family=SimSun-ExtB Name=SimSun-ExtB style=0 fileName=C:\Windows\Fonts\SIMSUNB.TTF
-> ** TrueType Font: Family=Sylfaen Name=Sylfaen style=0 fileName=C:\Windows\Fonts\sylfaen.ttf

And therein lies the tragic of the issue: on Windows “DialogInput” includes the font “MingLiU” which in turn represents Unicode code point U+0092 as right single quotation mark (see here).

Conclusion
I appologize for falsely accusing SQL Developer to do something strange when it is perfectly fine. On the other hand, it’s utterly beyond me why someone would design a font to represent a PRIVATE USE TWO Unicode code point to be a printable, otherwise perfectly well-defined character.
When in doubt, change the SQL Developer properties to a physical font you know or get familiar with the code points it supports – know your font!

Explicit Default – SecureFile parameter change

While introducing a new value for parameter DB_SECUREFILE in Oracle 12c the default value has changed as well (see doc).
With the new value “PREFERRED” all LOBs are created as SecureFile LOBs if not explicitly specified otherwise.

Just out of curiosity I created two databases, one with default “PREFERRED” the other with “PERMITTED” as it was the default in 11g.
Check out what happens to the Oracle data dictionary:

DB_SECUREFILE = PREFERRED

SQL> select owner, securefile, count(*) from dba_lobs group by owner, securefile order by owner;
OWNER                 SECUREFILE CNT
--------------------- ---------- ----------
CTXSYS                YES        4
GSMADMIN_INTERNAL     YES        1
OJVMSYS               NO         1
OUTLN                 NO         1
SYS                   NO         120
SYS                   YES        75
SYSTEM                NO         21
SYSTEM                YES        2
WMSYS                 YES        8
XDB                   NO         2
XDB                   YES        291

DB_SECUREFILE = PERMITTED

SQL> select owner, securefile, count(*) from dba_lobs group by owner, securefile order by owner;
OWNER                 SECUREFILE CNT
--------------------- ---------- ----------
CTXSYS                NO         4
GSMADMIN_INTERNAL     NO         1
OJVMSYS               NO         1
OUTLN                 NO         1
SYS                   NO         194
SYS                   YES        1
SYSTEM                NO         23
WMSYS                 NO         8
XDB                   NO         286
XDB                   YES        7

Quick thoughts:

  1. With every release study the New Features Guide carefully. Oracle’s not always waving a big red flag when “minor” things change.
  2. Do I like varying data dictionary definitions? No. I’m going to take great care when creating or migrating databases that they all look alike.
  3. What funny side effects we will encounter?
  4. I can see where Oracle is coming from, but if it is me I always try to be as explicit as possible and not relying on instance settings. When developing software and my customer base is, let’s say bigger than five I want to make sure all they’re schema deployments look the same.

Foot note: All test have been run on Oracle 12.1.0.2

Mashing up audit parameters

This is a short follow-up on my earlier post Don’t ditch AUDIT_TRAIL prematurely.
As it turns out AUDIT_TRAIL is not the only parameter that still affects the behaviour when Unified Auditing is enabled.
Since we have reset all “old” audit parameters except AUDIT_TRAIL the configuration looks like this:

SQL> show parameter audit
NAME                      TYPE        VALUE
------------------------- ----------- --------------------------------
audit_file_dest           string      /u01/app/oracle/admin/DEV1/adump
audit_sys_operations      boolean     TRUE
audit_syslog_level        string
audit_trail               string      DB

Although AUDIT_SYS_OPERATIONS defaults to TRUE we wouldn’t expect audit files being written – we’re using Unified Auditing after all.
To my surprise the Oracle database still writes OS audit files. E.g. here on my playground VM I see MMON slave process writing a file.

ll /u01/app/oracle/admin/DEV1/adump/
-rw-r-----.  1 oracle oinstall 2637 Jan 25 12:59 DEV1_m001_4546_20150125125939459040143795.aud

On customer systems I’ve also seen audit files from Scheduler jobs Jnnn processes. I haven’t figured a pattern yet and it seems to be different on various platforms. On Windows there’s a lot more recorded in the Windows Event Log (mainly AWR activity) than there’s written to ADUMP on Linux.
Solaris is a different story again. What I’ve found reproducible on Linux is connecting with a JDBC client (SQL Developer) and run any query that fails at parse time. Strangely enough, this does not happen when using SQL*Plus (OCI client). E.g.:

Sun Jan 25 14:15:33 2015 +01:00
LENGTH : '175'
ACTION :[18] 'select K from dual'
DATABASE USER:[3] 'SYS'
PRIVILEGE :[6] 'SYSDBA'
CLIENT USER:[3] 'btr'
CLIENT TERMINAL:[7] 'unknown'
STATUS:[3] '904'
DBID:[10] '3434795880'

What gets written to the audit files is not what we have specified in the unified audit policies, so setting AUDIT_SYS_OPERATIONS to FALSE to prevent all the extra auditing appears to be safe. So far, I haven’t seen any adverse effect on the unified audit trail, but you may want to check the proper functioning on your platform and audit configuration yourself.

Foot note 1: If $ORACLE_BASE/admin/$ORACLE_SID/adump directory does not exists AUDIT_FILE_DEST is defaulting to $ORACLE_HOME/rdbms/audit.

Foot note 2: If AUDIT_FILE_DEST is set explicitly to a non-existing directory, you’ll see alert.log entries indicating that the OS audit trail could not be written: “OS Audit file could not be created; failing after 6 retries”

Foot note 3: All test have been run on Oracle 12.1.0.2

Update 10-May-2016: The issue is documented on MOS as bug 21133343 (see MOS note: 21133343.8). Patches are available for Linux x86-64 and AIX on Power.

Multitenant PDB connects with SID descriptor

When moving to the Oracle 12c multitenant architecture one implication is that you have to switch to using service names in your connection strings for PDBs. Even SYSDBA connects to PDBs must use SERVICE_NAME.
Well, not quite necessarily. I guess customers adopting this new technology have not all managed to update their scripts. Or, software vendors and in-house development have hard-coded connection strings that cannot be changed quickly.
So, Oracle hastily added a new listener parameter USE_SID_AS_SERVICE_listener. This will tell the listener to treat the SID in the connect descriptor as SERVICE_NAME.
As long as the SID in the client connect descriptor matches the service name registered on the listener the connection is established.

$ cat /u01/app/oracle/network/admin/listener.ora 
LISTENER =
	(ADDRESS_LIST =
		(ADDRESS = 
			(PROTOCOL = TCP)
			(Host = oel6ora12cdb1.localdomain)
			(Port = 1521)
		)
	)

USE_SID_AS_SERVICE_LISTENER = ON

I have two PDBs which are registered on the listener as follows.

$ lsnrctl status
...
Services Summary...
Service "CDB1" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
Service "testdb1" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
Service "testdb2" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...

My client tnsnames.ora holds entries for both PDBs, one with SERVICE_NAME (TESTDB1) and one with SID (TESTDB2).

$ cat /u01/app/oracle/network/admin/tnsnames.ora 
TESTDB1 =
  (DESCRIPTION =
	(ADDRESS = (PROTOCOL = TCP)(HOST = oel6ora12cdb1.localdomain)(PORT = 1521))
	(CONNECT_DATA =
	  (SERVER = DEDICATED)
	  (SERVICE_NAME = TESTDB1)
	)
  )

TESTDB2 =
  (DESCRIPTION =
	(ADDRESS = (PROTOCOL = TCP)(HOST = oel6ora12cdb1.localdomain)(PORT = 1521))
	(CONNECT_DATA =
	  (SERVER = DEDICATED)
	  (SID = TESTDB2)
	)
  )

Next, I connect to both PDBs using above TNS entries.

$ sqlplus /nolog

SQL> connect system/manager@PDB1
Connected.

SQL> connect system/manager@PDB2
Connected.

Well, that’s all fine.
But, what’s going to happen if we set the DB_DOMAIN, then the db domain is appended to the service name. Ultimately, the SID parameter you have in the client connect descriptor does not match the service name anymore.

Since you most likely use above described feature because you cannot change the client connect descriptor this solution does not work anymore.
Back to square one.

Just for fun let’s try it anyway…but of course, for it to work I have to adjust the SID in the TNS entry for TESTDB2.

SQL> show parameter db_domain

NAME				     TYPE	 VALUE
------------------------------------ ----------- ------------------------------
db_domain			     string	 localdomain

On the listener there are the two services from my pluggable databases, TESTDB1 and TESTDB2.

$ lsnrctl status
...
Services Summary...
Service "CDB1.localdomain" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
Service "testdb1.localdomain" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
Service "testdb2.localdomain" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
The command completed successfully

Now, to match the service name I change the TNS entry for TESTDB2 to following:

TESTDB2.localdomain =
  (DESCRIPTION =
	(ADDRESS = (PROTOCOL = TCP)(HOST = oel6ora12cdb1.localdomain)(PORT = 1521))
	(CONNECT_DATA =
	  (SERVER = DEDICATED)
	  (SID = TESTDB2.localdomain)
	)
  )

At first sight it’s a bit strange to see the db domain in the SID parameter as this was never supposed to work this way. But at least we can connect again.

$ sqlplus system/manager@TESTDB2.localdomain
Connected.

I thought about creating a service using DBMS_SERVICE.CREATE_SERVICE but Oracle will always append the db domain to the service on the listener. And since a service in the format “PDB_NAME.DB_DOMAIN” already exists you will not be able to create it (ORA-44303: service name exists).

I file this under half-baked solution…(Not that I think this feature should by used anyway. Go get your scripts and applications fixed!)

Scripting mkstore

I had the pleasure to load a wallet (secure external password store) with around 180 credentials. Faced with a repeating task, I always try to automate things as much as possible or at least to the level I deem sensible. In this case my urge not to waste an afternoon typing in the same stuff over and over was overwhelming.
Unfortunately, the “mkstore” utility does not have a command switch to provide the wallet password. No problem, on Linux/Unix I would have just piped in the wallet password:

echo MyWallet-Password | mkstore -wrl /u01/app/oracle/etc/wallet/auth -createCredential DEV1.localdomain SYSTEM manager

Working on Windows using batch scripts piping does not work with “mkstore”. Here’s what I came up with:

echo | set /p="MyWallet-Password" > C:\Temp\walletpwd.txt

mkstore -wrl D:\app\oracle\etc\wallet\auth -createCredential DEV1.localdomain SYSTEM manager < C:\Temp\walletpwd.txt
...
...
...

Since we were generating new passwords the service account on all databases I went over the Enterprise Manager repository to generate the “mkstore” calls:

select
     'mkstore -wrl "D:\app\oracle\etc\wallet\auth" -createCredential '
  || tgt.target_name
  || ' srvacc "'
  || gen_pwd
  || '" < C:\Temp\walletpwd.txt'
from sysman.mgmt_targets tgt
  join sysman.mgmt_target_properties tgsid on (tgsid.target_guid = tgt.target_guid)
where tgt.target_type = 'oracle_database'
and tgsid.property_name = 'SID'
order by tgsid.property_value
;

Now I could just copy & paste the query output into a command line window. DONE.

Please remember to delete the temporary file containing the wallet password:

del /F /S /Q C:\Temp\walletpwd.txt

Although it saved me a lot of typing I’m not quite happy with the solution. The reason is I do temporarily write the wallet password to a file in plain text. I do not like that at all. And in some environments this might be considered a security breach, even if the file is only there for a couple of minutes.

If you come up with a solution that works without storing the wallet password in a file to redirect it to STDIN I’ll be happy to know about.

Don’t ditch AUDIT_TRAIL prematurely

When migrating to unified auditing in Oracle 12c I faced a strange behaviour in the recording of LOGON / LOGOFF actions.

As the Oracle 12c documentation points out, the AUDIT_* parameters have no effect once you enable unified auditing. Guess what I did in the good spirit of cleaning up obsolete settings:
I reset those parameters in the spfile after enabling unified auditing.

Enable unified auditing by relinking the oracle binary.

$ cd ${ORACLE_HOME}/rdbms/lib
$ make -f ins_rdbms.mk uniaud_on ioracle ORACLE_HOME=${ORACLE_HOME}

SQL> show parameter audit_trail
NAME          TYPE        VALUE
------------- ----------- ----------
audit_trail   string      NONE

I disabled all the Oracle built-in audit policies and created my own to record LOGON / LOGOFF actions.

SQL> create audit policy audpol_connection
     actions logon, logoff;

Audit policy created.

SQL> audit policy audpol_connection;

Audit succeeded.

SQL> select * from audit_unified_enabled_policies;

USER_NAME       POLICY_NAME          ENABL SUC FAI
--------------- -------------------- ----- --- ---
ALL USERS       AUDPOL_CONNECTION    BY    YES YES

SQL> select * from audit_unified_policies
     where policy_name = 'AUDPOL_CONNECTION'
     and audit_option like '%LOGO%';

POLICY_NAME          AUDIT CONDI AUDIT_OPTION    AUDIT_OPTION_TY OBJEC OBJEC OBJEC COMMO
-------------------- ----- ----- --------------- --------------- ----- ----- ----- -----
AUDPOL_CONNECTION    NONE  NONE  LOGOFF          STANDARD ACTION NONE  NONE  NONE
AUDPOL_CONNECTION    NONE  NONE  LOGON           STANDARD ACTION NONE  NONE  NONE

This shows, that my audit policy is enabled and should record LOGON and LOGOFF actions.

I also want the audit trail to be written immediately so it becomes visible in UNIFIED_AUDIT_TRAIL view without waiting for the flush to happen (or explicitly flushing it every time).

SQL> exec dbms_audit_mgmt.set_audit_trail_property(
         dbms_audit_mgmt.audit_trail_unified
       , dbms_audit_mgmt.audit_trail_write_mode
       , dbms_audit_mgmt.audit_trail_immediate_write
     )

PL/SQL procedure successfully completed.

For the sake of clean output let’s purge the existing audit trail.

SQL> exec dbms_audit_mgmt.clean_audit_trail(
         dbms_audit_mgmt.audit_trail_unified
       , false
     )

PL/SQL procedure successfully completed.

So far so good. Now begins the actual test case by connecting to the database from a different terminal.

$ sqlplus system/manager@dev1

SQL*Plus: Release 12.1.0.2.0 Production on Tue Oct 21 17:39:02 2014
Copyright (c) 1982, 2014, Oracle.  All rights reserved.
Last Successful login time: Tue Oct 21 2014 17:38:49 +02:00
Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics, Real Application Testing and Unified Auditing options

Note that I have not logged out yet…

Checking in my original session we cannot find any LOGON event recorded. I most definitively expected to see a LOGON event.

SQL> select audit_type, sessionid, dbusername, event_timestamp, action_name, return_code
     from unified_audit_trail
     where action_name like '%LOG%'
     order by event_timestamp;

no rows selected

After disconnecting from the other session the LOGOFF event becomes visible.

SQL> select audit_type, sessionid, dbusername, event_timestamp, action_name, return_code
     from unified_audit_trail
     where action_name like '%LOG%'
     order by event_timestamp;

AUDIT_TYPE SESSIONID    DBUSERNAME    EVENT_TIMESTAMP                 ACTION_NAME     RETURN_CODE
---------- ------------ ------------- ------------------------------- --------------- -----------
Standard   2253237941   SYSTEM        21-OCT-14 05.13.38.712564 PM    LOGOFF          0

I have run multiple different test with different clients, connection methods, etc. all ending with the same result: Oracle just wont record LOGON actions! We only get to see the LOGOFF event.

Against all odds and feeling a little desperate I eventually decided to set “AUDIT_TRAIL = DB” and give it a go.

SQL> show parameter audit_trail

NAME          TYPE        VALUE
------------- ----------- ----------
audit_trail   string      DB

I purged the audit trail and ran the same test as before again.

Connect from a differen terminal to the database.

$ sqlplus system/manager@dev1

SQL*Plus: Release 12.1.0.2.0 Production on Tue Oct 21 17:39:02 2014
Copyright (c) 1982, 2014, Oracle.  All rights reserved.
Last Successful login time: Tue Oct 21 2014 17:38:49 +02:00
Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics, Real Application Testing and Unified Auditing options

Check the unified audit trail – and there it is.

SQL> select audit_type, sessionid, dbusername, event_timestamp, action_name, return_code
     from unified_audit_trail
     where action_name like '%LOG%'
     order by event_timestamp;

AUDIT_TYPE SESSIONID    DBUSERNAME    EVENT_TIMESTAMP                 ACTION_NAME     RETURN_CODE
---------- ------------ ------------- ------------------------------- --------------- -----------
Standard   1706589952   SYSTEM        21-OCT-14 07.17.46.063184 PM    LOGON           0

After disconnecting from the other session I can again see the LOGOFF action.

SQL> select audit_type, sessionid, dbusername, event_timestamp, action_name, return_code
     from unified_audit_trail
     where action_name like '%LOG%'
     order by event_timestamp;

AUDIT_TYPE SESSIONID    DBUSERNAME    EVENT_TIMESTAMP                 ACTION_NAME     RETURN_CODE
---------- ------------ ------------- ------------------------------- --------------- -----------
Standard   1706589952   SYSTEM        21-OCT-14 07.17.46.063184 PM    LOGON           0
Standard   1706589952   SYSTEM        21-OCT-14 07.18.02.661384 PM    LOGOFF          0

In my opinion this does not work as designed as I believe Oracle actually wanted the AUDIT_* parameters to have no effect on unified auditing. I haven’t tested exhaustively but I imagine there might be other problems with unified auditing when AUDIT_TRAIL is set to NONE. So, test carefully whether all your auditing is working as expected.

Foot note: All test have been run on Oracle 12.1.0.2

Defeating ORA-12696 Double Encryption Turned On (tricking the ENVS)

This post is about configuring SQL*Net to support native SQL*Net encription and SSL authentication for different users at the same time. Imagine your security offices comes along and demands that every connection to the database must be encrypted and it must be enforced by the database. The application server (e.g. Weblogic) already authenticates itself to the database using SSL certificates, so the network traffic is already encrypted. All other connects use username/password authentication and must be encrypted by native SQL*Net encryption. The database server enforces encryption by setting SQLNET.ENCRYPTION_SERVER = REQUIRED. From a security point of view this is paramount.

Going ahead and configure everything according docs and white papers I bet you’ll end up with “ORA-12696 Double Encryption Turned On, login disallowed”.

[side note]
If SQLNET.ENCRYPTION_SERVER is set to REQUIRED then the client gets “ORA-28865: SSL connection closed”, but once you start digging in the SQL*Net trace file of the server process you’ll find ORA-12696.
Funnily, if SQLNET.ENCRYPTION_SERVER is set to REQUESTED then the client actually gets ORA-12696 reported.
[/side note]

The basic problem is, that there is only one $TNS_ADMIN location with one sqlnet.ora containing all the configuration. Ultimately, we need to find a way to use multiple $TNS_ADMIN locations and separate the sqlnet.ora configurations for the different listening end points/services.
One solution would be to fire up two listeners with different environment settings. Note that the server processes spawned by the listener inherit the properties from it, that being permissions and environment variables.
Following I’m going to show a solution with one single listener using a little known feature: specifying ENVS parameter in the static listener SID list.

But first let’s have a look a the current configuration which is giving us some trouble. There’s a listener configuration with two listening end points (one for TCP and one for TCPS) and a mixed configuration in sqlnet.ora.
My $TNS_ADMIN is pointing to /u01/app/oracle/network/admin which is the default location set in the oracle’s user profile and is used when starting up the listener.

$ cat /u01/app/oracle/network/admin/listener.ora

LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = oel6ora12dev1.localdomain)(PORT = 2483))
    )
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCPS)(HOST = oel6ora12dev1.localdomain)(PORT = 2484))
    )
  )
SSL_CLIENT_AUTHENTICATION=FALSE
WALLET_LOCATION = (SOURCE = (METHOD = FILE) (METHOD_DATA = (DIRECTORY=/u01/app/oracle/admin/DEV1/wallet/auth)))

$ cat /u01/app/oracle/network/admin/sqlnet.ora

SQLNET.ENCRYPTION_SERVER=requested
SQLNET.ENCRYPTION_TYPES_SERVER=(aes256)
SQLNET.AUTHENTICATION_SERVICES=(BEQ,TCPS)
SSL_CLIENT_AUTHENTICATION=TRUE
SSL_CIPHER_SUITES=(SSL_RSA_WITH_AES_256_CBC_SHA)
WALLET_LOCATION = (SOURCE = (METHOD=FILE)(METHOD_DATA = (DIRECTORY = /u01/app/oracle/admin/DEV1/wallet/auth)))

If we try to connect from a client using SSL authentication it errors with “ORA-12696 Double Encryption Turned On, login disallowed”. Connecting using username/password works just fine and the network traffic is properly encrypted.

To be able to have different SQL*Net configurations we first need to create a separate directory structure. In this case I just create it next to the current network/admin directory and separate the configuration into two sqlnet.ora files:
$ mkdir /u01/app/oracle/network/adminssl
$ vi /u01/app/oracle/network/admin/sqlnet.ora

SQLNET.ENCRYPTION_SERVER=requested
SQLNET.ENCRYPTION_TYPES_SERVER=(aes256)

$ vi /u01/app/oracle/network/adminssl/sqlnet.ora

SQLNET.AUTHENTICATION_SERVICES=(BEQ,TCPS)
SSL_CLIENT_AUTHENTICATION=TRUE
SSL_CIPHER_SUITES=(SSL_RSA_WITH_AES_256_CBC_SHA)
WALLET_LOCATION = (SOURCE = (METHOD=FILE)(METHOD_DATA = (DIRECTORY = /u01/app/oracle/admin/DEV1/wallet/auth)))

Now, I need to tell the listener to set the proper environment when spawning server processes for incomming connection requests. This is done by specifying the TNS_ADMIN location in the ENVS parameter:

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = DEV1_SSL.localdomain)
      (ORACLE_HOME = /u01/app/oracle/product/ora12101)
      (SID_NAME = DEV1)
      (ENVS="TNS_ADMIN=/u01/app/oracle/network/adminssl")
    )
  )

Now, whenever a connection is established via service DEV1_SSL.localdomain the listener exports the environment varialbe TNS_ADMIN before spawning the server process. The server process then looks up the environment variable and reads the sqlnet.ora file in /u01/app/oracle/network/adminssl.
As you can see, next to using different listening end points I also present an addtitional service on the listener.
Depending on which service and end point the client uses it gets either native SQL*Net encryption or SSL authentication including SSL encryption.

On the client side I use either one of the following TNS entries, depending on which authentication method the user uses.

DEV1_SSL.LOCALDOMAIN =
  (DESCRIPTION =
    (ADDRESS_LIST=
      (ADDRESS = (PROTOCOL = TCPS)(HOST = oel6ora12dev1.localdomain)(PORT = 2484))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = DEV1_SSL.localdomain)
      (SECURITY=(SSL_SERVER_CERT_DN="CN=DEV1.localdomain"))
    )
  )

DEV1_NSE.LOCALDOMAIN =
  (DESCRIPTION =
    (ADDRESS_LIST=
      (ADDRESS = (PROTOCOL = TCP)(HOST = oel6ora12dev1.localdomain)(PORT = 2483))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = DEV1.localdomain)
    )
  )

$ sqlplus /@DEV1_SSL
or
$ sqlplus system/manager@DEV1_NSE

Setting different values for TNS_ADMIN at the time of process spawning is useful in other situations, too. For instance, this technique can be applied if you have one listener for multiple databases and each database has its wallet.

In Grid Infrastructure environment there is also the possibility to set environment variables in Oracle Clusterware which is probably more comfortable to handle:

srvctl setenv database -d  -t TNS_ADMIN=

Foot note: All test have been run on Oracle 12.1.0.1

Deferred Segement Creation PARALLEL

Following up on Chris Antognini’s findings regarding deferred segment creation (here and here) I noticed another restriction that still finds no mention even in the latest Oracle documentation. I vaguely remember to have come across some issues in lower 11gR2 releases and some of them have been fixed in later patch sets. The problem went along the line of:

alter session set deferred_segment_creation = true;

create table t2 parallel 4
as 
  select /*+ parallel (t1, 4) */ *
  from t1 where 1 = 2
;

select segment_name, segment_type from user_segments where segment_name = 'T1';
SEGMENT_NAME SEGMENT_TYPE
------------ ------------
T1           TABLE

Note: This test war run against Oracle 11.2.0.1 as this particular issue is fixed in at least 11.2.0.4 and later

Now, why in gods name would you run CTAS in parallel when you know the query does not return any rows you might think.
Well, the problem becomes more subtle when we start combining features, namely partitioning and parallel DDL. Following I have prepared a few test cases to demonstrate the issue.

First let’s create the source table to select from:

create table t1
as
  select
      rownum id
    , round(dbms_random.value(1,4), 0) code
  from dual connect by level <= 100
;

The CODE column contains four distinct number values ranging from 1 to 4.

Next, we create a list partitioned table by using CTAS (create table as select) from our source table. All parts of this SQL run serially:

create table t3
segment creation deferred
partition by list (code) (
    partition code_1 values (1)
  , partition code_2 values (2)
  , partition code_3 values (3)
  , partition code_4 values (4)
)
as
  select *
  from t1
  where code = 1
;

select segment_name, segment_type, partition_name from dba_segments where owner = 'TESTUSER' and segment_name = 'T3' order by segment_name, partition_name;
SEGMENT_NAME SEGMENT_TYPE      PARTITION_NAME
------------ ---------------   --------------
T3           TABLE PARTITION   CODE_1

This works as expected. Only the relevant partition was materialized as a segment to hold the data for CODE value 1.

This time, we create the same table structure using parallel DDL:

create table t4
segment creation deferred
parallel 4
partition by list (code) (
    partition code_1 values (1)
  , partition code_2 values (2)
  , partition code_3 values (3)
  , partition code_4 values (4)
)
as
  select *
  from t1
  where code = 1
;

select segment_name, segment_type, partition_name from dba_segments where owner = 'TESTUSER' and segment_name = 'T4' order by segment_name, partition_name;
SEGMENT_NAME SEGMENT_TYPE      PARTITION_NAME
------------ ---------------   --------------
T4           TABLE PARTITION   CODE_1
T4           TABLE PARTITION   CODE_2
T4           TABLE PARTITION   CODE_3
T4           TABLE PARTITION   CODE_4

Oracle has materialized all four partitions even though only one of them contains data. The same also happens when the source query doesn’t return any rows, e.g. when CODE = 5.

Usually, there’s not much sense in running DDL in parallel while the SELECT providing the data runs serially. I have left out parallel processing of the SELECT part for simplicity as my tests have shown that it doesn’t affect the segment creation in any way.

Foot note: All test have been run on Oracle 12.1.0.2 and 11.2.0.4 except when where otherwise

SQL Developer oddity on Windows

Recently, I have come across a bit of a strange behaviour in SQL Developer when verifying the correctness of the data entered into a database from various clients (Linux / Windows / Solaris) -> think of client characterset in NLS_LANG.
In various MOS notes Oracle propagates the use of SQL Developer as a “known good client” to check data in the database, e.g. 1628060.1.
The one thing that struck me was when SQL Deloper displayed a right single quotation mark instead of a blank square symbol “[]” (representing a non-printable character).

Before we go to the test case I need to clarify a few points:

  • Databases tested 11.2.0.3 and 11.2.0.4
  • All databases running AL32UTF8 database character set
  • All databases running AL16UTF16 national character set
  • SQL Developer version tested: 4.0.2.15

Now, let’s look what happens if I run following query:

select unistr('\0092') char_display, dump(unistr('\0092'), 16) char_dump from dual;

Result on Linux:

linux_sqldev

Result on Windows:

windows_sqldev

Basically, I’m asking the system to display Unicode code point U+0092 which is a private control character and is not associated with a printable symbol.
In my opinion the Linux version is returning the right result while the one on Windows clearly does some code page conversion.
So, if you’re running SQL Developer on Windows it might no longer be a “known good client” as for sure the result as shown is unexpected.

Definer vs. invoker rights – check out SYS_CONTEXT

The introduction
This is showing the difference between definer and invoker rights in terms of current user and current schema – what context is the code running in.

The setup
In schema JOHN create two stored procedures one with definer rights the other with invoker rights both printing the same SYS_CONTEXT parameters.

SQL> create or replace procedure print_user_info_def
is
begin
  dbms_output.put_line('USER: '||user);
  dbms_output.put_line('SESSION_USER: '||sys_context('userenv', 'session_user'));
  dbms_output.put_line('CURRENT_USER: '||sys_context('userenv', 'current_user'));
  dbms_output.put_line('CURRENT_SCHEMA: '||sys_context('userenv', 'current_schema'));
end print_user_info_def;
/
SQL> create or replace procedure print_user_info_inv
authid current_user
is
begin
  dbms_output.put_line('USER: '||user);
  dbms_output.put_line('SESSION_USER: '||sys_context('userenv', 'session_user'));
  dbms_output.put_line('CURRENT_USER: '||sys_context('userenv', 'current_user'));
  dbms_output.put_line('CURRENT_SCHEMA: '||sys_context('userenv', 'current_schema'));
end print_user_info_inv;
/

The result
Log-in with user JANE and run the two stored procedures owned by JOHN

SQL> exec john.print_user_info_def
USER: JANE
SESSION_USER: JANE
CURRENT_USER: JOHN
CURRENT_SCHEMA: JOHN
SQL> exec john.print_user_info_inv
USER: JANE
SESSION_USER: JANE
CURRENT_USER: JANE
CURRENT_SCHEMA: JANE

Foot note: All tests have been run on a 10g 10.2.0.4 database.