IBMi (AS400) fans only - How to Sniff User Access

If you need to monitor user access on your IBM i system, you can use SQL to query the history log.

Note: At the end of this post, you'll find a request directly addressed to you, the readers.



Here’s a simple example of how you can do this:



message_id IN ('CPF1164', 'CPF1124')



SELECT MESSAGE_ID,
       MESSAGE_TIMESTAMP,
       FROM_USER,
       MESSAGE_TEXT
    FROM TABLE (
            QSYS2.HISTORY_LOG_INFO()
        )
    WHERE message_id IN ('CPF1164', 'CPF1124')
          AND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
    ORDER BY MESSAGE_TIMESTAMP



In this query:

- QSYS2.HISTORY_LOG_INFO(): This function retrieves information from the history log.

-  message_id IN ('CPF1164', 'CPF1124') : Filters the results to include only specific message IDs, which indicate sign-on and sign-off events.

- FROM_USER in ('QSECOFR', 'QSYS', 'QSYSOPR'): Filters the results to include only messages from specific users.



Update 28/07/2024: A Smart Enhancement

Thanks to my reader Greg, we can make an intelligent enhancement to our query. By refining the list of users to those with specific administrative permissions, we ensure more accurate monitoring.



Here’s the improved query:

SELECT MESSAGE_ID,
       MESSAGE_TIMESTAMP,
       FROM_USER,
       MESSAGE_TEXT
    FROM TABLE (
            QSYS2.HISTORY_LOG_INFO()
        )

    WHERE message_id IN ('CPF1164', 'CPF1124')
          AND FROM_USER IN (SELECT user_name
                  FROM QSYS2.USER_INFO_BASIC
                  WHERE POSSTR(special_authorities, '*ALLOBJ') > 0
                        OR POSSTR(special_authorities, '*SECADM') > 0
                        OR POSSTR(special_authorities, '*IOSYSCFG') >0)
    ORDER BY MESSAGE_TIMESTAMP

This adjustment filters the users to only those with the administrative permissions *ALLOBJ, *SECADM, and *IOSYSCFG.
Thank you Greg.


Update 30/07/2024: Another Smart Enhancement (Improved Performance with JOIN)

A reader suggested an enhancement to the original query for better performance. Instead of using the "IN" operator, we can join the tables. This approach has been tested and proven to return results faster. Below is the updated SQL query:


SELECT history.MESSAGE_ID,
       history.MESSAGE_TIMESTAMP,
       history.FROM_USER,
       history.MESSAGE_TEXT

    FROM TABLE (
             QSYS2.HISTORY_LOG_INFO()
         ) history

         JOIN QSYS2.USER_INFO_BASIC usrInf
             ON history.FROM_USER = usrInf.USER_NAME
    WHERE history.MESSAGE_ID IN ('CPF1164', 'CPF1124')
          AND (POSSTR(usrInf.SPECIAL_AUTHORITIES, '*ALLOBJ') > 0
              OR POSSTR(usrInf.SPECIAL_AUTHORITIES, '*SECADM') > 0
              OR POSSTR(usrInf.SPECIAL_AUTHORITIES, '*IOSYSCFG') > 0)
    ORDER BY history.MESSAGE_TIMESTAMP;


This query helps you track the login and logout activities of critical system users.


Try running this on your system and see the results.



Starting from the suggested SQL, you can derive countless pieces of information thanks to the QSYS2.HISTORY_LOG_INFO() function - Utilize it as you see fit.

Monitoring Failed Login Attempts

Another important aspect to monitor is failed login attempts. Use the following SQL statements to create a view and retrieve the users who have unsuccessfully attempted to log in.


Create a View for User Information:


CREATE OR REPLACE VIEW yourLib/USR_LIST
AS SELECT *                          
     FROM QSYS2.USER_INFO;

Retrieve Users with Failed Login Attempts


SELECT *
FROM yourLib/USR_LIST
WHERE SIGN_ON_ATTEMPTS_NOT_VALID > 0
ORDER BY SIGN_ON_ATTEMPTS_NOT_VALID DESC;




SIGN_ON_ATTEMPTS_NOT_VALID

This will list users who have attempted to log in unsuccessfully, ordered by the number of invalid sign-on attempts.



Feel free to comment below if you have any questions or suggestions.

I appreciate all the comments made on this blog.

If any of you readers are aware of a way through the OS400 operating system to 'sniff' or monitor access by users with administrative permissions such as QSECOFR, QSYS, QSYSOPR, QPGMR and could share the solution here, it would be greatly appreciated.




Comments

  1. It's been a long while since I was told this, so I don't remember who told me, but, supposedly, creating your own views on a QSYS2 table is not advised. I dunno if it has to do with the QSYS2 table being replaced when the OS is upgraded? And if the upgrade can't replace it because there is a user view on it, the upgrade fails?

    ReplyDelete
    Replies
    1. You are right! To avoid such problems, it is generally recommended to create custom views and tables in QTEMP library, or ensure that you delete custom views and tables after the job is done. This practice helps prevent conflicts during system updates and maintains better control over custom objects.

      Thank you for bringing this up!

      Delete
  2. To qualify the list to users that have "administrative permissions", you can replace your clause AND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
    with
    AND FROM_USER in (
    select user_name
    from USER_INFO_BASIC
    where posstr(special_authorities, '*ALLOBJ') > 0
    or posstr(special_authorities, '*SECADM') > 0
    or posstr(special_authorities, '*IOSYSCFG') > 0
    )

    ReplyDelete
    Replies
    1. Hi Greg,

      I have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.

      Thanks for your valuable contribution. It is greatly appreciated!

      Delete
  3. In order to select users with "administrative permissions", you can replace the clause
    AND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
    with
    AND FROM_USER IN (
    select user_name
    from USER_INFO_BASIC
    where posstr(special_authorities, '*ALLOBJ') > 0
    or posstr(special_authorities, '*SECADM') > 0
    or posstr(special_authorities, '*IOSYSCFG') > 0
    )

    ReplyDelete
    Replies
    1. Hi Greg,

      I have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.

      Thanks for your valuable contribution. It is greatly appreciated!

      Delete
  4. Using an "IN" operating instead of a join will be a performance hit. Trying joining the two tables on the FROM_USER and the associated user_name column in the 2nd table. I think you'll see that it returns much faster.

    ReplyDelete
    Replies
    1. Thank you for your insightful suggestion! Implementing a join instead of an "IN" operator has indeed improved the performance of the query significantly.

      I have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.

      Your advice is greatly appreciated!

      Delete
  5. Nice SQL's. I would suggest that wherever the "user_info_basic" line is displayed, it has to be qualified with qsys2 like this "qsys2.user_info_basic". I had to do that to get a couple of these to run.

    ReplyDelete
    Replies
    1. Thank you so much for the feedback, M. Mayer! I appreciate your suggestion to qualify the "USER_INFO_BASIC" table with "QSYS2". It's a great point, and I'll be sure to update the SQL examples to reflect this improvement. Your input helps ensure that the queries run smoothly for everyone. Thanks again!

      Delete
  6. Where "USER_INFO_BASIC" is listed on the SQL's, qualifiy it with QSYS2. "QSYS2.USER_INFO_BASIC"

    ReplyDelete
    Replies
    1. Thank you so much for the feedback, M. Mayer! I appreciate your suggestion to qualify the "USER_INFO_BASIC" table with "QSYS2". It's a great point, and I'll be sure to update the SQL examples to reflect this improvement. Your input helps ensure that the queries run smoothly for everyone. Thanks again!

      Delete

Post a Comment

Popular posts from this blog

IBMi (AS400) fans only ' Efficient WRKSPLF with WSF - How to Search string into spooled files, Sort, and Generate PDFs on IBMi

IBMi (AS400) fans only ' Detecting and Handling Non-Printable Characters in DB2 SQL Using LOCATE() and REPLACE() Functions