| Security Advisory | 
    
    
      This package supports application communications with external TCP/IP-based servers using TCP/IP and can be used when PL/SQL code needs to use Internet protocols and/or e-mail. 
       
      A maximum of 16 connections, per session, is allowed. See Oracle Support Note:280838.1: ORA-30678 after executing UTL_TCP.OPEN_CONNECTION for details. 
       
      From our standpoint the description by Oracle above should have eliminated any thought of granting EXECUTE to PUBLIC. Unfortunately it didn't.
      There is not conceivable, rational, reason why someone whose only privilege is CONNECT SESSION should be writing anonymous blocks or PL/SQL objects that allow valuable data to use TCP/IP unless the goal is to enhance the ability to steal data.
      We advise, in the strongest possible terms, revoking EXECUTE from PUBLIC. | 
    
    
      |   | 
    
    
      | Recommended Security Rules | 
    
    
       
       NEVER
      
        - install an Oracle database without revoking EXECUTE granted to PUBLIC from this package.
 
       
       WITH GREAT CARE
      
        - Test the revocation in your version and edition of the database, with your application to verify that there is no legitimate for access to the package.
 
        - If there is a need require a written justification signed off by your organization's CISO.
 
       
       CAUTIONS 
      
        - Grant EXECUTE on this package to any user or role because doing so is in no way different from the approval that should be required when punching a hole in the firewall to grant access to the database zone directly from the internet.
 
        - If you do, be sure that if not already done you create a Network Access Control List in the database (see link at the bottom of this page) to severely restrict what hosts can be contacted by this package.
 
       
       | 
    
    
      |   | 
    
    
      | How Oracle Works | 
    
    
      When a privilege is granted to PUBLIC that means that any connected user, without regard to their need or privileges can access that object. In the case of a legacy built-in package that means they can use any of the 17 objects it contains. 
       
      The code at right will verify that EXECUTE has been granted to PUBLIC. | 
      SQL> SELECT grantee, privilege 
        2  FROM dba_tab_privs 
        3  WHERE table_name = 'UTL_TCP'; 
       
      GRANTEE   PRIVILEGE 
      --------  ---------- 
      PUBLIC    EXECUTE | 
    
    
      Objects that are dependent upon UTL_TCP, unless constructed with dynamic or embedded SQL, which will be extremely rate, can be found in DBA_DEPENDENCIES. 
       
      To revoke EXECUTE from PUBLIC you must be absolutely sure you are not breaking anything in the database or your application. | 
      Only these two objects in an Oracle 18.3 database appear to require a grant of EXECUTE to remain valid. 
       
      SQL> SELECT owner, name 
        2  FROM dba_dependencies 
        3  WHERE referenced_name = 'UTL_TCP' 
        4  AND referenced_owner <> owner 
        5  AND referenced_name <> name 
        6* ORDER BY 1; 
       
      OWNER              NAME 
      -----------------  -------------------- 
      GSMADMIN_INTERNAL  DBMS_GSM_CLOUDADMIN 
      MDSYS              SEM_RDFCTX_DR 
       
      So try this, and if it work, test extensively before promoting to used environments 
       
      SQL> SELECT COUNT(*) 
        2  FROM dba_objects 
         3  WHERE status = 'INVALID'; 
       
      If you have invalid objects fix them before proceeding. 
       
      SQL>  GRANT execute ON utl_tcp TO gsmadmin_internal; 
      SQL> GRANT execute ON utl_tcp TO mdsys; 
      SQL> REVOKE execute ON utl_tcp FROM PUBLIC; 
       
      SQL> SELECT COUNT(*) 
        2  FROM dba_objects 
        3  WHERE status = 'INVALID'; 
       
      If you have invalid objects identify the root cause and fix before proceeding.
      If you are unable to determine and address the root cause bring in an Oracle Database security expert to address the issue and teach your team how to do it in the future. | 
    
    
      The Email code at right shows how UTL_TCP can be utilized to create ad hoc connection to any remote host unless a restriction is created and maintained in the database or firewall Access Control List (ACL).  
       
      If the functionality is not required the safest and most permanent thing to do is to just revoke the privilege where not required. 
       
      Note the connection names and email addresses are not valid ... you need to substitute your own for the code to function. | 
      CREATE OR REPLACE PROCEDURE send_mail( 
      sender    IN VARCHAR2, 
      recipient IN VARCHAR2, 
      message   IN VARCHAR2) 
      AUTHID DEFINER IS 
       mailhost   VARCHAR2(30) := 'smtp.dbsecworx.com'; 
       smtp_error  EXCEPTION; 
       mail_conn  utl_tcp.connection; 
       
       -- embedded procedure 
       PROCEDURE smtp_command(command IN VARCHAR2, ok IN VARCHAR2 DEFAULT '250') IS 
        response VARCHAR2(256); 
        len      PLS_INTEGER; 
       BEGIN 
         len := utl_tcp.write_line(mail_conn, command); 
         response := utl_tcp.get_line(mail_conn); 
         dbms_output.put_line(response); 
         response := SUBSTR(response,1,3); 
         IF (response <> ok) THEN 
           RAISE smtp_error; 
         END IF; 
       END smtp_command; 
       -- end embedded procedure 
      BEGIN 
        mail_conn := utl_tcp.open_connection(remote_host => mailhost, 
        remote_port => 25, charset => 'US7ASCII'); 
        smtp_command('HELO ' || mailhost); 
        smtp_command('MAIL FROM: ' || sender); 
        smtp_command('RCPT TO: ' || recipient); 
        smtp_command('DATA', '354'); 
        smtp_command(message); 
        smtp_command('QUIT', '221'); 
        utl_tcp.close_connection(mail_conn); 
      END send_mail; 
      / 
       
      exec send_mail('testing@dbsecworx.com', 'testing@dbsecworx.com', 'Test'); | 
    
    
      |   | 
    
    
      | UTL_TCP Package Information | 
    
    
      | AUTHID | 
      CURRENT_USER | 
    
    
      | Constants | 
      
        
          
            
              | Name | 
              Data Type | 
              Value | 
             
            
              | CRLF | 
              VARCHAR2(2 CHAR) | 
              unistr('\000D\000A') | 
             
             | 
    
    
      | Data Types | 
      TYPE connection IS RECORD ( 
      remote_host VARCHAR2(255),  -- Remote host name 
      remote_port PLS_INTEGER,    -- Remote port number 
      local_host  VARCHAR2(255),  -- Local host name 
      local_port  PLS_INTEGER,    -- Local port number 
      charset     VARCHAR2(30),   -- Character set for on-the-wire comm. 
      newline     VARCHAR2(2),    -- Newline character sequence 
      tx_timeout  PLS_INTEGER,    -- Transfer time-out value (in seconds) 
      private_sd  PLS_INTEGER)    -- For internal use only 
      / | 
    
    
      | Dependencies | 
      
        
          
            
              | DBMS_AQELM | 
              UTL_CALL_STACK | 
              UTL_SMTP | 
             
            
              | DBMS_ISCHED | 
              UTL_MAIL | 
              UTL_TCP_LIB | 
             
            
              | SCHEDULER$_JOB_EVENT_HANDLER | 
                | 
                | 
             
             | 
    
    
      | Documented | 
      Yes | 
    
    
      | Exceptions | 
      
        
          
            
              | Error Code | 
              Reason | 
             
            
              | ORA-24247 | 
              network_access_denied_errcode: Network access denied | 
             
            
              | ORA-29258 | 
              buffer_too_small_errcode: Buffer is too small for I/O | 
             
            
              | ORA-29259 | 
              end_of_input_errcode: End of input from the connection | 
             
            
              | ORA-29260 | 
              network_error_errcode: Network error | 
             
            
              | ORA-29261 | 
              bad_argument_errcode: Bad argument passed in API call | 
             
            
              | ORA-29275 | 
              partial_multibyte_char_errcode: A partial multi-byte character found | 
             
            
              | ORA-29276 | 
              transfer_timeout: Transfer time-out occurred | 
             
             | 
    
    
      | First Available | 
      8.1.7 | 
    
    
      | Security Model | 
      Owned by SYS with EXECUTE granted to PUBLIC | 
    
    
      | Source | 
      {ORACLE_HOME}/rdbms/admin/utltcp.sql | 
    
    
      | Subprograms | 
      
         | 
    
    
      |   | 
    
    
      | AVAILABLE | 
    
    
      | Determines the number of bytes available for reading from a TCP/IP connection | 
      utl_tcp.available( 
      c       IN OUT NOCOPY connection, 
      timeout IN     PLS_INTEGER DEFAULT 0) 
      RETURN PLS_INTEGER; | 
    
    
      | See READ_TEXT Demo Below | 
    
    
      |   | 
    
    
      | CLOSE_ALL_CONNECTIONS | 
    
    
      | Closes all open TCP/IP connections | 
      utl_tcp.close_all_connections; | 
    
    
      exec utl_tcp.close_all_connections; | 
    
    
      |   | 
    
    
      | CLOSE_CONNECTION | 
    
    
      | Closes a TCP/IP connection | 
      utl_tcp.close_connection(c IN OUT NOCOPY connection); | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | FLUSH | 
    
    
      | Immediately transmits all the output data in the output queue to the connection | 
      utl_tcp.flush(c IN OUT NOCOPY connection); | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | GET_LINE | 
    
    
      | A convenient form of the read functions, which return the data read instead of the amount of data rekad | 
      utl_tcp.get_line( 
      c           IN OUT NOCOPY connection, 
      remove_crlf IN     BOOLEAN DEFAULT FALSE, 
      peek        IN     BOOLEAN DEFAULT FALSE) 
      RETURN VARCHAR2; | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | GET_LINE_NCHAR | 
    
    
      | A convenient form of the read functions, which return the data read instead of the amount of data read | 
      utl_tcp.get_line_nchar( 
      c           IN OUT NOCOPY connection, 
      remove_crlf IN     BOOLEAN DEFAULT FALSE, 
      peek        IN     BOOLEAN DEFAULT FALSE) 
      RETURN NVARCHAR2; | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | GET_RAW | 
    
    
      | A convenient form of the read functions, which return the data read instead of the amount of data read | 
      utl_tcp.get_raw( 
      c    IN OUT NOCOPY connection, 
      len  IN     PLS_INTEGER DEFAULT 1, 
      peek IN     BOOLEAN DEFAULT FALSE) 
      RETURN RAW; | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | GET_TEXT | 
    
    
      | A convenient form of the read functions, which return the data read instead of the amount of data read | 
      utl_tcp.get_text( 
      c    IN OUT NOCOPY connection, 
      len  IN     PLS_INTEGER DEFAULT 1, 
      peek IN     BOOLEAN DEFAULT FALSE) 
      RETURN VARCHAR2; | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | GET_TEXT_NCHAR | 
    
    
      | A convenient form of the read functions, which return the data read instead of the amount of data read | 
      utl_tcp.get_text_nchar( 
      c    IN OUT NOCOPY connection, 
      len  IN     PLS_INTEGER DEFAULT 1, 
      peek IN     BOOLEAN DEFAULT FALSE) 
      RETURN NVARCHAR2; | 
    
    
      | See OPEN_CONNECTION Demo Below | 
    
    
      |   | 
    
    
      | OPEN_CONNECTION | 
    
    
      | Opens a connection to a TCP/IP service | 
      utl_tcp.open_connection( 
      remote_host     VARCHAR2, 
      remote_port     PLS_INTEGER, 
      local_host      VARCHAR2    DEFAULT NULL, 
      local_port      PLS_INTEGER DEFAULT NULL, 
      in_buffer_size  PLS_INTEGER DEFAULT NULL, 
      out_buffer_size PLS_INTEGER DEFAULT NULL, 
      charset         VARCHAR2    DEFAULT NULL, 
      newline         VARCHAR2    DEFAULT
      CRLF, 
      tx_timeout      PLS_INTEGER DEFAULT NULL, 
      wallet_path     VARCHAR2    DEFAULT NULL, 
      wallet_password VARCHAR2    DEFAULT NULL) 
      RETURN connection; | 
    
    
      set serveroutput on 
       
      spool c:\temp\utl_tcp.txt 
       
      DECLARE 
       c      utl_tcp.connection;  -- TCP/IP connection to the web server 
       retval PLS_INTEGER; 
      BEGIN 
        c := utl_tcp.open_connection(remote_host => 'www.morganslibrary.org', remote_port => 80, charset => 'US7ASCII'); 
        utl_tcp.secure_connection(c); 
       
        retval := utl_tcp.write_line(c, 'GET / HTTP/1.0');  -- send request 
        retval := utl_tcp.write_line(c); 
       
        BEGIN 
          LOOP 
            dbms_output.put_line(utl_tcp.get_line(c, TRUE));  -- read result 
          END LOOP; 
        EXCEPTION 
          WHEN  utl_tcp.end_of_input THEN 
            NULL;  -- end of input 
          WHEN OTHERS THEN 
            NULL; 
        END; 
       
        BEGIN 
          utl_tcp.flush(c); 
        EXCEPTION 
          WHEN OTHERS THEN 
            NULL; 
        END; 
       
        utl_tcp.close_connection(c); 
      END; 
      / 
       
      spool off | 
    
    
      | A function that ping's ports to determine whether they exist | 
      CREATE OR REPLACE FUNCTION ping (pHostName VARCHAR2, pPort NUMBER DEFAULT 1000) 
      RETURN VARCHAR2 AUTHID DEFINER IS 
       tcpCnx utl_tcp.connection; 
       cOk    CONSTANT VARCHAR2(2) := 'OK'; 
       cFail  CONSTANT VARCHAR2(5) := 'ERROR'; 
      BEGIN 
        tcpCnx := utl_tcp.open_connection (pHostName, pPort); 
        utl_tcp.close_connection(tcpCnx); 
        RETURN cOk; 
      EXCEPTION 
        WHEN utl_tcp.network_error THEN 
          IF (UPPER(SQLERRM) LIKE '%HOST%') THEN 
            RETURN cFail; 
          ELSIF (UPPER(SQLERRM) LIKE '%LISTENER%') THEN 
            RETURN cOk; 
          ELSE 
            RAISE; 
          END IF; 
      WHEN OTHERS THEN 
        RAISE; 
      END ping; 
      / 
       
      SELECT ping('usps997lt.usa.morganslibrary.com', 1521) 
      FROM dual; | 
    
    
      |   | 
    
    
      | READ_LINE | 
    
    
      | Reads a text line from a TCP/IP connection | 
      utl_tcp.read_line( 
      c           IN OUT NOCOPY connection, 
      data        IN OUT NOCOPY VARCHAR2 CHARACTER SET ANY_CS, 
      remove_crlf IN            BOOLEAN DEFAULT FALSE, 
      peek        IN            BOOLEAN DEFAULT FALSE) 
      RETURN PLS_INTEGER; | 
    
    
      | TBD | 
    
    
      |   | 
    
    
      | READ_RAW | 
    
    
      | Reads binary data from a TCP/IP connection | 
      utl_tcp.read_raw( 
      c    IN OUT NOCOPY connection, 
      data IN OUT NOCOPY RAW, 
      len  IN            PLS_INTEGER DEFAULT 1, 
      peek IN            BOOLEAN DEFAULT FALSE) 
      RETURN PLS_INTEGER; | 
    
    
      | TBD | 
    
    
      |   | 
    
    
      | READ_TEXT | 
    
    
      | Reads text data from a TCP/IP connection | 
      utl_tcp.read_text( 
      c    IN OUT NOCOPY connection, 
      data IN OUT NOCOPY VARCHAR2 CHARACTER SET ANY_CS, 
      len  IN            PLS_INTEGER DEFAULT 1, 
      peek IN            BOOLEAN DEFAULT FALSE) 
      RETURN PLS_INTEGER; | 
    
    
      DECLARE 
       c    utl_tcp.connection; 
       data VARCHAR2(256); 
       len  PLS_INTEGER; 
      BEGIN 
        c := utl_tcp.open_connection(remote_host => 'www.morganslibrary.org', remote_port => 80, charset => 'US7ASCII'); 
        LOOP 
          IF (utl_tcp.available(c) > 0) THEN 
            len := utl_tcp.read_text(c, data, 256); 
          ELSE 
            ---do some other things 
            NULL; 
          END IF; 
        END LOOP; 
        utl_tcp.close_connection(c); 
      END; 
      / | 
    
    
      |   | 
    
    
      | SECURE_CONNECTION | 
    
    
      | Secures a TCP/IP connection using SSL/TLS. Requires an Oracle Wallet spec. | 
      utl_tcp.secure_connection( 
      c           IN OUT NOCOPY connection 
      secure_host IN            VARCHAR2 DEFAULT NULL); | 
    
    
      | See OPEN_CONNECTION Demo Above | 
    
    
      |   | 
    
    
      | WRITE_LINE | 
    
    
      | Writes a text line to a TCP/IP connection | 
      utl_tcp.write_line( 
      c    IN OUT NOCOPY connection, 
      data IN     VARCHAR2 CHARACTER SET ANY_CS DEFAULT NULL) 
      RETURN PLS_INTEGER; | 
    
    
      | See OPEN_CONNECTION Demo Above | 
    
    
      |   | 
    
    
      | WRITE_RAW | 
    
    
      | Writes binary data to a TCP/IP connection | 
      utl_tcp.write_raw( 
      c    IN OUT NOCOPY connection, 
      data IN            RAW, 
      len  IN            PLS_INTEGER DEFAULT NULL) 
      RETURN PLS_INTEGER; | 
    
    
      | TBD | 
    
    
      |   | 
    
    
      | WRITE_TEXT | 
    
    
      | Writes text data to a TCP/IP connection | 
      utl_tcp.write_text( 
      c    IN OUT NOCOPY connection, 
      data IN     VARCHAR2 CHARACTER SET ANY_CS, 
      len  IN     PLS_INTEGER DEFAULT NULL) 
      RETURN PLS_INTEGER; | 
    
    
      | TBD |