We can use EXTPROC to invoke an external programs, like C.
There is an simple example about setting:

How to Dynamically Execute a SQL Script from a Stored Procedure? (Doc ID 341586.1)

Be aware that the IPC listener and extproc_connection_data is not mandatory, without IPC listener, a local extproc process will be started to communicate with server process. The C code I am using can be found:

How To Run Extproc Agent Under A Less Privileged Account (Doc ID 1204853.1)

SQL> create or replace directory libdir as '$ORACLE_HOME/lib';

Directory created.

SQL> 
SQL> CREATE or replace LIBRARY shell_lib is 'shell.so' in libdir;
  2  /

Library created.

SQL> CREATE OR REPLACE PACKAGE BD_UTIL as
  2  FUNCTION shell_ejecucion(comando in char) return pls_integer;
  3  END;
  4  /


Package created.

SQL> SQL> CREATE OR REPLACE PACKAGE BODY BD_UTIL is
  2  FUNCTION shell_ejecucion(comando in char) return pls_integer
  3  as language C
  4  name "sh"
  5  library shell_lib
  6  parameters (comando string,return int);
  7  END bd_util;
  8  /

Package body created.

SQL> declare                                 
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
PL/SQL procedure successfully completed.

SQL> host ls -l /tmp/test.txt
-rw-r--r--. 1 oracle oinstall 29 Feb 25 22:10 /tmp/test.txt

-- with IPC listener, and extproc_connection_data
-- we can see there is a EXTPROC being spawned by Listener, and LOCAL=NO
# ps -ef|grep extproc
oracle    2732     1  0 22:41 ?        00:00:00 /opt/oracle/product/12.1/dbhome_1/bin/extprocPLSExtProc (LOCAL=NO)
root      2741 26893  0 22:41 pts/2    00:00:00 grep --color=auto extproc

-- after remove entry in tnsnames.ora
-- a little delay, the process will be spawned after timeout, with LOCAL=YES
# ps -ef|grep extproc
oracle    2887     1  0 22:42 ?        00:00:00 /opt/oracle/product/12.1/dbhome_1/bin/extproc (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=BEQ)))

Usually, the EXTPROC will be folked in “oracle” which is the owner of database and listener. For security consideration, we normally want to extproc can be spawned in the less privilege users.
So, for the situation above, there are two different way to configure this.

If the process is spawned by database, we can use credential.

SQL> begin
  2  dbms_credential.create_credential(
  3  credential_name => 'run_marc',
  4  username => 'marc',
  5  password => '123456'
  6  );
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> CREATE or replace LIBRARY shell_lib is 'shell.so' in libdir credential run_marc;
  2  /

Library created.

SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
0

PL/SQL procedure successfully completed.

SQL> host ls -l /tmp/test.txt
-rw-rw-rw-. 1 marc users 29 Feb 25 22:52 /tmp/test.txt

We change the owner and permission of “/tmp/test.txt”, let user marc cannot write it.

# chown oracle.oinstall /tmp/test.txt
# chmod 644 /tmp/test.txt 

SQL> set serveroutput on
SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
256         -- an error reported.

PL/SQL procedure successfully completed.

Unfortunately, credential is not working if the extproc is spawned by listener.
I add extproc_connection_data in tnsnames.ora

SQL> host rm -rf /tmp/test.txt

SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
0

PL/SQL procedure successfully completed.

-- the file is still generated by oracle
SQL> host ls -l /tmp/test.txt
-rw-r--r--. 1 oracle oinstall 29 Feb 25 23:13 /tmp/test.txt

How To Run Extproc Agent Under A Less Privileged Account (Doc ID 1204853.1)
gives a way but not working, even the process is spawned as marc

[root@testenv bin]# ps -ef|grep extproc
marc      5936     1  0 23:24 ?        00:00:00 /opt/oracle/product/12.1/dbhome_1/bin/extprocPLSExtProc (LOCAL=NO)

-- rerun the test code.

-- the file is still generated by oracle, even the credential is to marc
SQL> host ls -l /tmp/test.txt
-rw-r--r--. 1 oracle oinstall 29 Feb 25 23:25 /tmp/test.txt

Due to the extproc which spawned by listener will inherent the priliveges of listener. So, we need to move IPC listener to the diffenent user.

$ whoami          
marc
$ . oraenv
ORACLE_SID = [o12102] ? o12102
The Oracle base remains unchanged with value /opt/oracle
$ export TNS_ADMIN=/home/marc/admin
$ lsnrctl start extproc_listener

LSNRCTL for Linux: Version 12.1.0.2.0 - Production on 25-FEB-2019 23:33:02

Copyright (c) 1991, 2014, Oracle.  All rights reserved.

Starting /opt/oracle/product/12.1/dbhome_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 12.1.0.2.0 - Production
System parameter file is /home/marc/admin/listener.ora
Log messages written to /opt/oracle/diag/tnslsnr/testenv/extproc_listener/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1)))

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1)))
STATUS of the LISTENER
------------------------
Alias                     extproc_listener
Version                   TNSLSNR for Linux: Version 12.1.0.2.0 - Production
Start Date                25-FEB-2019 23:33:02
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /home/marc/admin/listener.ora
Listener Log File         /opt/oracle/diag/tnslsnr/testenv/extproc_listener/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
  Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully
$ ps -ef|grep tns
root        22     2  0 Feb24 ?        00:00:00 [netns]
oracle    2353     1  0 22:39 ?        00:00:00 /opt/oracle/product/12.1/dbhome_1/bin/tnslsnr LISTENER -inherit
marc      6582     1  0 23:33 ?        00:00:00 /opt/oracle/product/12.1/dbhome_1/bin/tnslsnr extproc_listener -inherit

-- change tnsnames.ora:
extproc_connection_data =
(DESCRIPTION =
  (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1)))
  (CONNECT_DATA = (SID = PLSExtProc) (PRESENTATION = RO))
)


SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> host ls -l /tmp/test.txt
-rw-r--r--. 1 marc users 29 Feb 25 23:36 /tmp/test.txt

Be careful, from Oracle database version 12.2.0.1 and above, the “extproc” agent no longer resolves the EXTPROC_CONNECTION_DATA entry in tnsnames.ora for security reaons and the agent gets spawned by the database itself.

Configuring Listener To Spawn EXTPROC Agent Is Not Working in 12.2, EXTPROC_CONNECTION_DATA Is Not Getting Resolved. (Doc ID 2326592.1)

We have to use agent after 12.2 to use the listener to spawn an EXTPROC

$ cat listener.ora 
LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC2))
      (ADDRESS = (PROTOCOL = TCP)(HOST = testenv)(PORT = 1521))
      (ADDRESS = (PROTOCOL = TCPS)(HOST = testenv)(PORT = 2484))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (SID_NAME = PLSExtProc)
      (ORACLE_HOME=/opt/oracle/product/12.2/dbhome_1)
      (PROGRAM=/opt/oracle/product/12.2/dbhome_1/bin/extproc)
      (ENVS="EXTPROC_DLLS=ANY", "LD_LIBRARY_PATH=/home/oracle/lib")
    )
  )
  
$ cat tnsnames.ora 
extproc_connection_data =
(DESCRIPTION =
  (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC2)))
  (CONNECT_DATA = (SID = PLSExtProc) (PRESENTATION = RO))
)

This time, I put shell.so to /home/oracle/lib

SQL> create or replace directory libdir as '/home/oracle/lib';

Directory created.

SQL> 
SQL> CREATE or replace LIBRARY shell_lib is 'shell.so' in libdir;
  2  /

Library created.

SQL> declare                                 
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-28595: Extproc agent : Invalid DLL Path
ORA-06512: at "SYS.BD_UTIL", line 2
ORA-06512: at line 4

Due to /home/oracle/lib is not on the LD_LIBRARY_PATH in .bash_profile, so, the EXTPROC spawned by database cannot find this one in 12.2

We can follow DOC 1109144.1 to set

How to Execute Both 32-bit and 64-bit External Procedures Within The Same 11.2 and Above Database (Doc ID 1109144.1)

Be aware that the tnsname cannot be extproc_connection_data

SQL> DATABASE LINK agent_link USING 'extproc_connection_data';

Database link created.

SQL> CREATE or replace LIBRARY shell_lib is 'shell.so' in libdir AGENT 'agent_link';

Library created.

SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-28595: Extproc agent : Invalid DLL Path
ORA-06512: at "SYS.BD_UTIL", line 2
ORA-06512: at line 4

-- rename the tnsname to extproc_connection_data2
SQL> DROP DATABASE LINK agent_link;

Database link dropped.

SQL> CREATE DATABASE LINK agent_link USING 'extproc_connection_data2';

Database link created.

SQL> CREATE or replace LIBRARY shell_lib is 'shell.so' in libdir AGENT 'agent_link';

Library created.

SQL> declare
  2  retorno binary_integer;
  3  begin
  4  retorno := bd_util.shell_ejecucion('/bin/date > /tmp/test.txt');
  5  dbms_output.put_line(retorno);
  6  end;
  7  /
0

PL/SQL procedure successfully completed.

Looks like it is impossible to disable database to spawn an EXTPROC. So, in order to control the behavior of EXTPROC

  1. always use listener to spawn EXTPROC
  2. use credential to restrict the EXTPROC the database spawned in the less privileged user.
  3. do not store .so file in the LD_LIBRARY_PATH, using listener to locate .so
  4. use ENVS=”EXTPROC_DLLS=ONLY:/home/oracle/lib/shell.so” in listener to restrict the .so the user can use.
  5. never give create library and grant the use of library to end user directly, use procedure to encapsulate it, and only give the execute privilege on this procedure to end user.