Sudoers Time Stamp file: C++11/STL parsing library

This spec can be used to parse sudo time stamp files located in directories such as /run/sudo/ts/$USER or /var/lib/sudo/ts/$USER.

KS implementation details

License: CC0-1.0

This page hosts a formal specification of Sudoers Time Stamp file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++11/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.bin", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    sudoers_ts_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.records() // => get records

C++11/STL source code to parse Sudoers Time Stamp file

sudoers_ts.h

#pragma once

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <memory>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * This spec can be used to parse sudo time stamp files located in directories
 * such as /run/sudo/ts/$USER or /var/lib/sudo/ts/$USER.
 * \sa https://www.sudo.ws/docs/man/1.8.27/sudoers_timestamp.man/ Source
 */

class sudoers_ts_t : public kaitai::kstruct {

public:
    class record_v2_t;
    class ts_flag_t;
    class record_v1_t;
    class timespec_t;
    class record_t;

    enum ts_type_t {
        TS_TYPE_GLOBAL = 1,
        TS_TYPE_TTY = 2,
        TS_TYPE_PPID = 3,
        TS_TYPE_LOCKEXCL = 4
    };

    sudoers_ts_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

private:
    void _read();
    void _clean_up();

public:
    ~sudoers_ts_t();

    class record_v2_t : public kaitai::kstruct {

    public:

        record_v2_t(kaitai::kstream* p__io, sudoers_ts_t::record_t* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~record_v2_t();

    private:
        ts_type_t m_type;
        std::unique_ptr<ts_flag_t> m_flags;
        uint32_t m_auth_uid;
        uint32_t m_sid;
        std::unique_ptr<timespec_t> m_start_time;
        std::unique_ptr<timespec_t> m_ts;
        uint32_t m_ttydev;
        bool n_ttydev;

    public:
        bool _is_null_ttydev() { ttydev(); return n_ttydev; };

    private:
        uint32_t m_ppid;
        bool n_ppid;

    public:
        bool _is_null_ppid() { ppid(); return n_ppid; };

    private:
        sudoers_ts_t* m__root;
        sudoers_ts_t::record_t* m__parent;

    public:

        /**
         * record type
         */
        ts_type_t type() const { return m_type; }

        /**
         * record flags
         */
        ts_flag_t* flags() const { return m_flags.get(); }

        /**
         * user ID that was used for authentication
         */
        uint32_t auth_uid() const { return m_auth_uid; }

        /**
         * ID of the user's terminal session, if present (when type is TS_TTY)
         */
        uint32_t sid() const { return m_sid; }

        /**
         * start time of the session leader for records of type TS_TTY or of the parent process for records of type TS_PPID
         */
        timespec_t* start_time() const { return m_start_time.get(); }

        /**
         * actual time stamp, from a monotonic time source
         */
        timespec_t* ts() const { return m_ts.get(); }

        /**
         * device number of the terminal associated with the session
         */
        uint32_t ttydev() const { return m_ttydev; }

        /**
         * ID of the parent process
         */
        uint32_t ppid() const { return m_ppid; }
        sudoers_ts_t* _root() const { return m__root; }
        sudoers_ts_t::record_t* _parent() const { return m__parent; }
    };

    class ts_flag_t : public kaitai::kstruct {

    public:

        ts_flag_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~ts_flag_t();

    private:
        uint64_t m_reserved0;
        bool m_anyuid;
        bool m_disabled;
        uint64_t m_reserved1;
        sudoers_ts_t* m__root;
        kaitai::kstruct* m__parent;

    public:

        /**
         * Reserved (unused) bits
         */
        uint64_t reserved0() const { return m_reserved0; }

        /**
         * ignore uid
         */
        bool anyuid() const { return m_anyuid; }

        /**
         * entry disabled
         */
        bool disabled() const { return m_disabled; }

        /**
         * Reserved (unused) bits
         */
        uint64_t reserved1() const { return m_reserved1; }
        sudoers_ts_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    class record_v1_t : public kaitai::kstruct {

    public:

        record_v1_t(kaitai::kstream* p__io, sudoers_ts_t::record_t* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~record_v1_t();

    private:
        ts_type_t m_type;
        std::unique_ptr<ts_flag_t> m_flags;
        uint32_t m_auth_uid;
        uint32_t m_sid;
        std::unique_ptr<timespec_t> m_ts;
        uint32_t m_ttydev;
        bool n_ttydev;

    public:
        bool _is_null_ttydev() { ttydev(); return n_ttydev; };

    private:
        uint32_t m_ppid;
        bool n_ppid;

    public:
        bool _is_null_ppid() { ppid(); return n_ppid; };

    private:
        sudoers_ts_t* m__root;
        sudoers_ts_t::record_t* m__parent;

    public:

        /**
         * record type
         */
        ts_type_t type() const { return m_type; }

        /**
         * record flags
         */
        ts_flag_t* flags() const { return m_flags.get(); }

        /**
         * user ID that was used for authentication
         */
        uint32_t auth_uid() const { return m_auth_uid; }

        /**
         * session ID associated with tty/ppid
         */
        uint32_t sid() const { return m_sid; }

        /**
         * time stamp, from a monotonic time source
         */
        timespec_t* ts() const { return m_ts.get(); }

        /**
         * device number of the terminal associated with the session
         */
        uint32_t ttydev() const { return m_ttydev; }

        /**
         * ID of the parent process
         */
        uint32_t ppid() const { return m_ppid; }
        sudoers_ts_t* _root() const { return m__root; }
        sudoers_ts_t::record_t* _parent() const { return m__parent; }
    };

    class timespec_t : public kaitai::kstruct {

    public:

        timespec_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~timespec_t();

    private:
        int64_t m_sec;
        int64_t m_nsec;
        sudoers_ts_t* m__root;
        kaitai::kstruct* m__parent;

    public:

        /**
         * seconds
         */
        int64_t sec() const { return m_sec; }

        /**
         * nanoseconds
         */
        int64_t nsec() const { return m_nsec; }
        sudoers_ts_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    class record_t : public kaitai::kstruct {

    public:

        record_t(kaitai::kstream* p__io, sudoers_ts_t* p__parent = nullptr, sudoers_ts_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~record_t();

    private:
        uint16_t m_version;
        uint16_t m_len_record;
        std::unique_ptr<kaitai::kstruct> m_payload;
        bool n_payload;

    public:
        bool _is_null_payload() { payload(); return n_payload; };

    private:
        sudoers_ts_t* m__root;
        sudoers_ts_t* m__parent;
        std::string m__raw_payload;
        std::unique_ptr<kaitai::kstream> m__io__raw_payload;

    public:

        /**
         * version number of the timestamp_entry struct
         */
        uint16_t version() const { return m_version; }

        /**
         * size of the record in bytes
         */
        uint16_t len_record() const { return m_len_record; }
        kaitai::kstruct* payload() const { return m_payload.get(); }
        sudoers_ts_t* _root() const { return m__root; }
        sudoers_ts_t* _parent() const { return m__parent; }
        std::string _raw_payload() const { return m__raw_payload; }
        kaitai::kstream* _io__raw_payload() const { return m__io__raw_payload.get(); }
    };

private:
    std::unique_ptr<std::vector<std::unique_ptr<record_t>>> m_records;
    sudoers_ts_t* m__root;
    kaitai::kstruct* m__parent;

public:
    std::vector<std::unique_ptr<record_t>>* records() const { return m_records.get(); }
    sudoers_ts_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

sudoers_ts.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "sudoers_ts.h"

sudoers_ts_t::sudoers_ts_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_records = nullptr;
    _read();
}

void sudoers_ts_t::_read() {
    m_records = std::unique_ptr<std::vector<std::unique_ptr<record_t>>>(new std::vector<std::unique_ptr<record_t>>());
    {
        int i = 0;
        while (!m__io->is_eof()) {
            m_records->push_back(std::move(std::unique_ptr<record_t>(new record_t(m__io, this, m__root))));
            i++;
        }
    }
}

sudoers_ts_t::~sudoers_ts_t() {
    _clean_up();
}

void sudoers_ts_t::_clean_up() {
}

sudoers_ts_t::record_v2_t::record_v2_t(kaitai::kstream* p__io, sudoers_ts_t::record_t* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = nullptr;
    m_start_time = nullptr;
    m_ts = nullptr;
    _read();
}

void sudoers_ts_t::record_v2_t::_read() {
    m_type = static_cast<sudoers_ts_t::ts_type_t>(m__io->read_u2le());
    m_flags = std::unique_ptr<ts_flag_t>(new ts_flag_t(m__io, this, m__root));
    m_auth_uid = m__io->read_u4le();
    m_sid = m__io->read_u4le();
    m_start_time = std::unique_ptr<timespec_t>(new timespec_t(m__io, this, m__root));
    m_ts = std::unique_ptr<timespec_t>(new timespec_t(m__io, this, m__root));
    n_ttydev = true;
    if (type() == sudoers_ts_t::TS_TYPE_TTY) {
        n_ttydev = false;
        m_ttydev = m__io->read_u4le();
    }
    n_ppid = true;
    if (type() == sudoers_ts_t::TS_TYPE_PPID) {
        n_ppid = false;
        m_ppid = m__io->read_u4le();
    }
}

sudoers_ts_t::record_v2_t::~record_v2_t() {
    _clean_up();
}

void sudoers_ts_t::record_v2_t::_clean_up() {
    if (!n_ttydev) {
    }
    if (!n_ppid) {
    }
}

sudoers_ts_t::ts_flag_t::ts_flag_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void sudoers_ts_t::ts_flag_t::_read() {
    m_reserved0 = m__io->read_bits_int_be(6);
    m_anyuid = m__io->read_bits_int_be(1);
    m_disabled = m__io->read_bits_int_be(1);
    m_reserved1 = m__io->read_bits_int_be(8);
}

sudoers_ts_t::ts_flag_t::~ts_flag_t() {
    _clean_up();
}

void sudoers_ts_t::ts_flag_t::_clean_up() {
}

sudoers_ts_t::record_v1_t::record_v1_t(kaitai::kstream* p__io, sudoers_ts_t::record_t* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = nullptr;
    m_ts = nullptr;
    _read();
}

void sudoers_ts_t::record_v1_t::_read() {
    m_type = static_cast<sudoers_ts_t::ts_type_t>(m__io->read_u2le());
    m_flags = std::unique_ptr<ts_flag_t>(new ts_flag_t(m__io, this, m__root));
    m_auth_uid = m__io->read_u4le();
    m_sid = m__io->read_u4le();
    m_ts = std::unique_ptr<timespec_t>(new timespec_t(m__io, this, m__root));
    n_ttydev = true;
    if (type() == sudoers_ts_t::TS_TYPE_TTY) {
        n_ttydev = false;
        m_ttydev = m__io->read_u4le();
    }
    n_ppid = true;
    if (type() == sudoers_ts_t::TS_TYPE_PPID) {
        n_ppid = false;
        m_ppid = m__io->read_u4le();
    }
}

sudoers_ts_t::record_v1_t::~record_v1_t() {
    _clean_up();
}

void sudoers_ts_t::record_v1_t::_clean_up() {
    if (!n_ttydev) {
    }
    if (!n_ppid) {
    }
}

sudoers_ts_t::timespec_t::timespec_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void sudoers_ts_t::timespec_t::_read() {
    m_sec = m__io->read_s8le();
    m_nsec = m__io->read_s8le();
}

sudoers_ts_t::timespec_t::~timespec_t() {
    _clean_up();
}

void sudoers_ts_t::timespec_t::_clean_up() {
}

sudoers_ts_t::record_t::record_t(kaitai::kstream* p__io, sudoers_ts_t* p__parent, sudoers_ts_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m__io__raw_payload = nullptr;
    _read();
}

void sudoers_ts_t::record_t::_read() {
    m_version = m__io->read_u2le();
    m_len_record = m__io->read_u2le();
    n_payload = true;
    switch (version()) {
    case 1: {
        n_payload = false;
        m__raw_payload = m__io->read_bytes((len_record() - 4));
        m__io__raw_payload = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_payload));
        m_payload = std::unique_ptr<record_v1_t>(new record_v1_t(m__io__raw_payload.get(), this, m__root));
        break;
    }
    case 2: {
        n_payload = false;
        m__raw_payload = m__io->read_bytes((len_record() - 4));
        m__io__raw_payload = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_payload));
        m_payload = std::unique_ptr<record_v2_t>(new record_v2_t(m__io__raw_payload.get(), this, m__root));
        break;
    }
    default: {
        m__raw_payload = m__io->read_bytes((len_record() - 4));
        break;
    }
    }
}

sudoers_ts_t::record_t::~record_t() {
    _clean_up();
}

void sudoers_ts_t::record_t::_clean_up() {
    if (!n_payload) {
    }
}