::nsmcp::LogTool ::nsmcp::LogTool arguments execute validate ::nsmcp::Tool ::nsmcp::Tool describe execute name permission validate ::nsmcp::LogTool->::nsmcp::Tool ::nsmcp::ServerDescribe ::nsmcp::ServerDescribe execute ::nsmcp::ServerDescribe->::nsmcp::Tool ::nsmcp::StatsTool ::nsmcp::StatsTool available encode unavailable ::nsmcp::StatsTool->::nsmcp::Tool ::nsmcp::AccessSummaryTool ::nsmcp::AccessSummaryTool execute options validate ::nsmcp::AccessSummaryTool->::nsmcp::Tool ::nsmcp::SmtpLogTool ::nsmcp::SmtpLogTool decode execute init options parse path recordTriples redact validate ::nsmcp::SmtpLogTool->::nsmcp::Tool ::nx::Object ::nx::Object ::nsmcp::Tool->::nx::Object

Class ::nsmcp::SmtpLogTool

::nsmcp::SmtpLogTool[i] create ... \
           [ -authorizer:required authorizer:required ] \
           [ -description (default "Read recent nssmtpd sendlog or eventlog records from the current server's configured SMTP module. Defaults to eventlog, the last 60 minutes, and at most 20 newest matching records. Reads at most 1 MiB from the current file; does not scan rotated files or certify full-window coverage when earlier bytes remain unread. Reports malformed, oversized and unfinished lines separately. Filters use exact event/session/status matches; contains is a literal substring in the redacted record. Returns SMTP envelope addresses and diagnostic metadata, never message bodies. Peer addresses are masked unless includePeerAddresses is true and diagnostics.logs.peers.read is granted. Event records belonging to another virtual server are excluded; sendlog has no per-record server field, so its scope is the configured source. Does not send mail or change SMTP settings.") ] \
           [ -inputSchema (default "{} object {type string object properties object { log object {type string string enum array {0 string event 1 string send} default string event} module object {type string string default string nssmtpd description string {Configured nssmtpd module name in the current virtual server.}} lastMinutes object {type string integer minimum number 1 maximum number 1440 default number 60} limit object {type string integer minimum number 1 maximum number 100 default number 20} session object {type string string description string {Exact eventlog session identifier; not available in sendlog.}} event object {type string string description string {Exact eventlog event name.}} status object {type string string description string {Exact SMTP status code, for example 451 or 550.}} contains object {type string string description string {Literal substring in the redacted record.}} includePeerAddresses object {type string boolean default boolean false} } additionalProperties boolean false}") ] \
           [ -maxLineBytes:integer (default "16384") ] \
           [ -maxReadBytes:integer (default "1048576") ] \
           [ -name (default "smtp_logs") ] \
           [ -outputSchema (default "") ] \
           [ -peerRedactor:required peerRedactor:required ] \
           [ -permission (default "diagnostics.logs.read") ] \
           [ -redactor:required redactor:required ]

Defined in /usr/local/ns/tcl/nsmcp/lib/smtp-tools.tcl

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nsmcp::Tool[i]
::nx::Class create ::nsmcp::SmtpLogTool \
     -superclass ::nsmcp::Tool

Methods (to be applied on instances)

  • execute (scripted, public)

     <instance of nsmcp::SmtpLogTool[i]> execute context arguments
    Parameters:
    context (required)
    arguments (required)

    Testcases:
    No testcase defined.
    set options [:options $arguments]
    set peers [dict get $options includePeerAddresses]
    if {$peers} {${:authorizer} require $context diagnostics.logs.peers.read}
    set server [$context server]
    set kind [dict get $options log]
    set module [dict get $options module]
    set source [:path $server $module $kind]
    set path [dict get $source path]
    set fields [list server string $server module string $module log string $kind  loggingEnabled boolean [dict get $source enabled] sourcePath string $path]
    if {$path eq "" || ![file isfile $path] || ![file readable $path]} {
        lappend fields available boolean false reason string {Configured SMTP log is missing or unreadable.}
        return [list {} object $fields]
    }
    set now [clock seconds]
    set from [expr {$now-60*[dict get $options lastMinutes]}]
    file stat $path before
    set end $before(size)
    set start [expr {max(0,$end-${:maxReadBytes})}]
    set channel [open $path rb]
    try {
        seek $channel $start
        set bytes [read $channel [expr {$end-$start}]]
    } finally {close $channel}
    set readBytes [string length $bytes]
    set discarded 0
    if {$start > 0} {
        # Discard the first fragment, even if it happens to be a full line.
        set first [string first \n $bytes]
        if {$first < 0} {set discarded $readBytes; set bytes ""} else {
            set discarded [expr {$first+1}]
            set bytes [string range $bytes $discarded end]
        }
    }
    set position [expr {$start+$discarded}]
    set pending [expr {$bytes ne "" && [string index $bytes end] ne "\n"}]
    set lines [split $bytes \n]
    set lines [lrange $lines 0 end-1]
    set malformed 0
    set oversized 0
    set otherServers 0
    set matches 0
    set records {}
    foreach line $lines {
        set offset $position
        incr position [expr {[string length $line]+1}]
        if {[string length $line] > ${:maxLineBytes}} {incr oversized; continue}
        if {$line eq ""} continue
        if {[catch {:parse $kind [encoding convertfrom utf-8 [string trimright $line \r]]} record]} {
            incr malformed
            continue
        }
        if {$kind eq "event" && [dict get $record recordedServer] ne $server} {incr otherServers; continue}
        set at [dict get $record timestampSeconds]
        if {$at < $from || $at > $now} continue
        set match true
        foreach key {session event status} {
            if {[dict exists $options $key] && [dict get $options $key] ne [dict get $record $key]} {set match false}
        }
        if {!$match} continue
        set record [:redact $record $peers]
        if {[dict exists $options contains] && [string first [dict get $options contains] $record] < 0} continue
        incr matches
        dict set record offset $offset
        dict set record endOffset $position
        lappend records $record
        set records [lrange $records end-[expr {[dict get $options limit]-1}] end]
    }
    set changed [catch {file stat $path after}]
    if {!$changed} {
        set changed [expr {[list $before(dev) $before(ino)] ne [list $after(dev) $after(ino)] || $after(size) < $end}]
    }
    if {$changed} {set records {}; set matches 0}
    set array {}
    set index 0
    foreach record [lreverse $records] {
        lappend array $index object [:recordTriples $record]
        incr index
    }
    set complete [expr {$start == 0 && !$pending && !$changed && !$malformed && !$oversized}]
    lappend fields available boolean true from number $from to number $now order string newest-first  records array $array matchesInTail number $matches omittedMatches number [expr {$matches-$index}]  coverage object [list sourceBytes number $end readBytes number $readBytes  initialOffset number $start examinedFromOffset number [expr {$start+$discarded}]  historyUnreadBytes number $start tailOnly boolean [expr {$start > 0}]  pending boolean $pending malformed number $malformed oversized number $oversized  otherServerRecords number $otherServers sourceChanged boolean $changed fileScanComplete boolean $complete rotatedFilesExamined boolean false]
    return [list {} object $fields]
  • validate (scripted, public)

     <instance of nsmcp::SmtpLogTool[i]> validate arguments
    Parameters:
    arguments (required)

    Testcases:
    No testcase defined.
    if {[catch {:options $arguments} message]} {
        return -code error -errorcode {NSMCP RPC -32602} $message
    }