diff --git a/webadmin/admin.go b/webadmin/admin.go index 5e0b67b..5f00839 100644 --- a/webadmin/admin.go +++ b/webadmin/admin.go @@ -2217,3 +2217,10 @@ func (Admin) TLSRPTSuppressExtend(ctx context.Context, id int64, until time.Time err := tlsrptdb.SuppressUpdate(ctx, id, until) xcheckf(ctx, err, "updating reporting address in suppresslist") } + +// LookupCid turns an ID from a Received header into a cid as used in logging. +func (Admin) LookupCid(ctx context.Context, recvID string) (cid string) { + v, err := mox.ReceivedToCid(recvID) + xcheckf(ctx, err, "received id to cid") + return fmt.Sprintf("%x", v) +} diff --git a/webadmin/admin.js b/webadmin/admin.js index 338a1ee..b7b3cbd 100644 --- a/webadmin/admin.js +++ b/webadmin/admin.js @@ -1028,6 +1028,14 @@ var api; const params = [id, until]; return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); } + // LookupCid turns an ID from a Received header into a cid as used in logging. + async LookupCid(recvID) { + const fn = "LookupCid"; + const paramTypes = [["string"]]; + const returnTypes = [["string"]]; + const params = [recvID]; + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); + } } api.Client = Client; api.defaultBaseURL = (function () { @@ -1596,6 +1604,9 @@ const index = async () => { let domain; let account; let localpart; + let recvIDFieldset; + let recvID; + let cidElem; dom._kids(page, crumbs('Mox Admin'), checkUpdatesEnabled ? [] : dom.p(box(yellow, 'Warning: Checking for updates has not been enabled in mox.conf (CheckUpdates: true).', dom.br(), 'Make sure you stay up to date through another mechanism!', dom.br(), 'You have a responsibility to keep the internet-connected software you run up to date and secure!', dom.br(), 'See ', link('https://updates.xmox.nl/changelog'))), dom.p(dom.a('Accounts', attr.href('#accounts')), dom.br(), dom.a('Queue', attr.href('#queue')), ' (' + queueSize + ')', dom.br()), dom.h2('Domains'), (domains || []).length === 0 ? box(red, 'No domains') : dom.ul((domains || []).map(d => dom.li(dom.a(attr.href('#domains/' + domainName(d)), domainString(d))))), dom.br(), dom.h2('Add domain'), dom.form(async function submit(e) { e.preventDefault(); @@ -1613,7 +1624,23 @@ const index = async () => { fieldset.disabled = false; } window.location.hash = '#domains/' + domain.value; - }, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), 'Domain', dom.br(), domain = dom.input(attr.required(''))), ' ', dom.label(style({ display: 'inline-block' }), 'Postmaster/reporting account', dom.br(), account = dom.input(attr.required(''))), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Localpart (optional)', attr.title('Must be set if and only if account does not yet exist. The localpart for the user of this domain. E.g. postmaster.')), dom.br(), localpart = dom.input()), ' ', dom.submitbutton('Add domain', attr.title('Domain will be added and the config reloaded. You should add the required DNS records after adding the domain.')))), dom.br(), dom.h2('Reports'), dom.div(dom.a('DMARC', attr.href('#dmarc/reports'))), dom.div(dom.a('TLS', attr.href('#tlsrpt/reports'))), dom.br(), dom.h2('Operations'), dom.div(dom.a('MTA-STS policies', attr.href('#mtasts'))), dom.div(dom.a('DMARC evaluations', attr.href('#dmarc/evaluations'))), dom.div(dom.a('TLS connection results', attr.href('#tlsrpt/results'))), + }, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), 'Domain', dom.br(), domain = dom.input(attr.required(''))), ' ', dom.label(style({ display: 'inline-block' }), 'Postmaster/reporting account', dom.br(), account = dom.input(attr.required(''))), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Localpart (optional)', attr.title('Must be set if and only if account does not yet exist. The localpart for the user of this domain. E.g. postmaster.')), dom.br(), localpart = dom.input()), ' ', dom.submitbutton('Add domain', attr.title('Domain will be added and the config reloaded. You should add the required DNS records after adding the domain.')))), dom.br(), dom.h2('Reports'), dom.div(dom.a('DMARC', attr.href('#dmarc/reports'))), dom.div(dom.a('TLS', attr.href('#tlsrpt/reports'))), dom.br(), dom.h2('Operations'), dom.div(dom.a('MTA-STS policies', attr.href('#mtasts'))), dom.div(dom.a('DMARC evaluations', attr.href('#dmarc/evaluations'))), dom.div(dom.a('TLS connection results', attr.href('#tlsrpt/results'))), dom.div(style({ marginTop: '.5ex' }), dom.form(async function submit(e) { + e.preventDefault(); + e.stopPropagation(); + try { + dom._kids(cidElem); + recvIDFieldset.disabled = true; + const cid = await client.LookupCid(recvID.value); + dom._kids(cidElem, cid); + } + catch (err) { + console.log({ err }); + window.alert('Error: ' + errmsg(err)); + } + finally { + recvIDFieldset.disabled = false; + } + }, recvIDFieldset = dom.fieldset(dom.label('Received ID', attr.title('The ID in the Received header that was added during incoming delivery.')), ' ', recvID = dom.input(attr.required('')), ' ', dom.submitbutton('Lookup cid', attr.title('Logging about an incoming message includes an attribute "cid", a counter identifying the transaction related to delivery of the message. The ID in the received header is an encrypted cid, which this form decrypts, after which you can look it up in the logging.')), ' ', cidElem = dom.span()))), // todo: routing, globally, per domain and per account dom.br(), dom.h2('DNS blocklist status'), dom.div(dom.a('DNSBL status', attr.href('#dnsbl'))), dom.br(), dom.h2('Configuration'), dom.div(dom.a('Webserver', attr.href('#webserver'))), dom.div(dom.a('Files', attr.href('#config'))), dom.div(dom.a('Log levels', attr.href('#loglevels'))), footer); }; diff --git a/webadmin/admin.ts b/webadmin/admin.ts index 438b637..a131114 100644 --- a/webadmin/admin.ts +++ b/webadmin/admin.ts @@ -265,6 +265,10 @@ const index = async () => { let account: HTMLInputElement let localpart: HTMLInputElement + let recvIDFieldset: HTMLFieldSetElement + let recvID: HTMLInputElement + let cidElem: HTMLSpanElement + dom._kids(page, crumbs('Mox Admin'), checkUpdatesEnabled ? [] : dom.p(box(yellow, 'Warning: Checking for updates has not been enabled in mox.conf (CheckUpdates: true).', dom.br(), 'Make sure you stay up to date through another mechanism!', dom.br(), 'You have a responsibility to keep the internet-connected software you run up to date and secure!', dom.br(), 'See ', link('https://updates.xmox.nl/changelog'))), @@ -329,6 +333,32 @@ const index = async () => { dom.div(dom.a('MTA-STS policies', attr.href('#mtasts'))), dom.div(dom.a('DMARC evaluations', attr.href('#dmarc/evaluations'))), dom.div(dom.a('TLS connection results', attr.href('#tlsrpt/results'))), + dom.div( + style({marginTop: '.5ex'}), + dom.form( + async function submit(e: SubmitEvent) { + e.preventDefault() + e.stopPropagation() + try { + dom._kids(cidElem) + recvIDFieldset.disabled = true + const cid = await client.LookupCid(recvID.value) + dom._kids(cidElem, cid) + } catch (err) { + console.log({err}) + window.alert('Error: ' + errmsg(err)) + } finally { + recvIDFieldset.disabled = false + } + }, + recvIDFieldset=dom.fieldset( + dom.label('Received ID', attr.title('The ID in the Received header that was added during incoming delivery.')), ' ', + recvID=dom.input(attr.required('')), ' ', + dom.submitbutton('Lookup cid', attr.title('Logging about an incoming message includes an attribute "cid", a counter identifying the transaction related to delivery of the message. The ID in the received header is an encrypted cid, which this form decrypts, after which you can look it up in the logging.')), ' ', + cidElem=dom.span(), + ), + ), + ), // todo: routing, globally, per domain and per account dom.br(), dom.h2('DNS blocklist status'), diff --git a/webadmin/api.json b/webadmin/api.json index 8dfc050..f59360a 100644 --- a/webadmin/api.json +++ b/webadmin/api.json @@ -1125,6 +1125,26 @@ } ], "Returns": [] + }, + { + "Name": "LookupCid", + "Docs": "LookupCid turns an ID from a Received header into a cid as used in logging.", + "Params": [ + { + "Name": "recvID", + "Typewords": [ + "string" + ] + } + ], + "Returns": [ + { + "Name": "cid", + "Typewords": [ + "string" + ] + } + ] } ], "Sections": [], diff --git a/webadmin/api.ts b/webadmin/api.ts index 3b62f5c..7bd5299 100644 --- a/webadmin/api.ts +++ b/webadmin/api.ts @@ -1535,6 +1535,15 @@ export class Client { const params: any[] = [id, until] return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void } + + // LookupCid turns an ID from a Received header into a cid as used in logging. + async LookupCid(recvID: string): Promise { + const fn: string = "LookupCid" + const paramTypes: string[][] = [["string"]] + const returnTypes: string[][] = [["string"]] + const params: any[] = [recvID] + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string + } } export const defaultBaseURL = (function() {