1*67b9b338Schristos /* $NetBSD: showq_json.c,v 1.4 2022/10/08 16:12:48 christos Exp $ */
2e262b48eSchristos
3e262b48eSchristos /*++
4e262b48eSchristos /* NAME
5e262b48eSchristos /* showq_json 8
6e262b48eSchristos /* SUMMARY
7e262b48eSchristos /* JSON queue status formatter
8e262b48eSchristos /* SYNOPSIS
9e262b48eSchristos /* void showq_json(
10e262b48eSchristos /* VSTREAM *showq)
11e262b48eSchristos /* DESCRIPTION
12e262b48eSchristos /* This function converts showq(8) daemon output to JSON format.
13e262b48eSchristos /* DIAGNOSTICS
14e262b48eSchristos /* Fatal errors: out of memory, malformed showq(8) daemon output.
15e262b48eSchristos /* LICENSE
16e262b48eSchristos /* .ad
17e262b48eSchristos /* .fi
18e262b48eSchristos /* The Secure Mailer license must be distributed with this software.
19e262b48eSchristos /* AUTHOR(S)
20e262b48eSchristos /* Wietse Venema
21e262b48eSchristos /* IBM T.J. Watson Research
22e262b48eSchristos /* P.O. Box 704
23e262b48eSchristos /* Yorktown Heights, NY 10598, USA
24e262b48eSchristos /*
25e262b48eSchristos /* Wietse Venema
26e262b48eSchristos /* Google, Inc.
27e262b48eSchristos /* 111 8th Avenue
28e262b48eSchristos /* New York, NY 10011, USA
29e262b48eSchristos /*--*/
30e262b48eSchristos
31e262b48eSchristos /* System library. */
32e262b48eSchristos
33e262b48eSchristos #include <sys_defs.h>
34e262b48eSchristos #include <stdlib.h>
35e262b48eSchristos #include <unistd.h>
36e262b48eSchristos #include <string.h>
37e262b48eSchristos #include <sysexits.h>
38e262b48eSchristos #include <ctype.h>
3933881f77Schristos #include <errno.h>
40e262b48eSchristos
41e262b48eSchristos /* Utility library. */
42e262b48eSchristos
43e262b48eSchristos #include <vstring.h>
44e262b48eSchristos #include <vstream.h>
45e262b48eSchristos #include <stringops.h>
46e262b48eSchristos #include <mymalloc.h>
47e262b48eSchristos #include <msg.h>
48e262b48eSchristos
49e262b48eSchristos /* Global library. */
50e262b48eSchristos
51e262b48eSchristos #include <mail_proto.h>
52e262b48eSchristos #include <mail_queue.h>
53e262b48eSchristos #include <mail_date.h>
54e262b48eSchristos #include <mail_params.h>
55e262b48eSchristos
56e262b48eSchristos /* Application-specific. */
57e262b48eSchristos
58e262b48eSchristos #include <postqueue.h>
59e262b48eSchristos
60e262b48eSchristos #define STR(x) vstring_str(x)
61e262b48eSchristos #define LEN(x) VSTRING_LEN(x)
62e262b48eSchristos
63e262b48eSchristos /* json_quote - quote JSON string */
64e262b48eSchristos
json_quote(VSTRING * result,const char * text)65e262b48eSchristos static char *json_quote(VSTRING *result, const char *text)
66e262b48eSchristos {
67e262b48eSchristos unsigned char *cp;
68e262b48eSchristos int ch;
69e262b48eSchristos
70e262b48eSchristos /*
71e262b48eSchristos * We use short escape sequences for common control characters. Note that
72e262b48eSchristos * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences
73e262b48eSchristos * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC
74e262b48eSchristos * 4627 complaint.
75e262b48eSchristos */
76e262b48eSchristos VSTRING_RESET(result);
77e262b48eSchristos for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) {
78e262b48eSchristos if (UNEXPECTED(ISCNTRL(ch))) {
79e262b48eSchristos switch (ch) {
80e262b48eSchristos case '\b':
81e262b48eSchristos VSTRING_ADDCH(result, '\\');
82e262b48eSchristos VSTRING_ADDCH(result, 'b');
83e262b48eSchristos break;
84e262b48eSchristos case '\f':
85e262b48eSchristos VSTRING_ADDCH(result, '\\');
86e262b48eSchristos VSTRING_ADDCH(result, 'f');
87e262b48eSchristos break;
88e262b48eSchristos case '\n':
89e262b48eSchristos VSTRING_ADDCH(result, '\\');
90e262b48eSchristos VSTRING_ADDCH(result, 'n');
91e262b48eSchristos break;
92e262b48eSchristos case '\r':
93e262b48eSchristos VSTRING_ADDCH(result, '\\');
94e262b48eSchristos VSTRING_ADDCH(result, 'r');
95e262b48eSchristos break;
96e262b48eSchristos case '\t':
97e262b48eSchristos VSTRING_ADDCH(result, '\\');
98e262b48eSchristos VSTRING_ADDCH(result, 't');
99e262b48eSchristos break;
100e262b48eSchristos default:
101e262b48eSchristos vstring_sprintf(result, "\\u%04X", ch);
102e262b48eSchristos break;
103e262b48eSchristos }
104e262b48eSchristos } else {
105e262b48eSchristos switch (ch) {
106e262b48eSchristos case '\\':
107e262b48eSchristos case '"':
108e262b48eSchristos VSTRING_ADDCH(result, '\\');
109e262b48eSchristos /* FALLTHROUGH */
110e262b48eSchristos default:
111e262b48eSchristos VSTRING_ADDCH(result, ch);
112e262b48eSchristos break;
113e262b48eSchristos }
114e262b48eSchristos }
115e262b48eSchristos }
116e262b48eSchristos VSTRING_TERMINATE(result);
117e262b48eSchristos
118e262b48eSchristos /*
119e262b48eSchristos * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with
120e262b48eSchristos * SMTPUTF8 disabled).
121e262b48eSchristos */
122e262b48eSchristos printable(STR(result), '?');
123e262b48eSchristos return (STR(result));
124e262b48eSchristos }
125e262b48eSchristos
126e262b48eSchristos /* json_message - report status for one message */
127e262b48eSchristos
format_json(VSTREAM * showq_stream)128e262b48eSchristos static void format_json(VSTREAM *showq_stream)
129e262b48eSchristos {
130e262b48eSchristos static VSTRING *queue_name = 0;
131e262b48eSchristos static VSTRING *queue_id = 0;
132e262b48eSchristos static VSTRING *addr = 0;
133e262b48eSchristos static VSTRING *why = 0;
134e262b48eSchristos static VSTRING *quote_buf = 0;
135e262b48eSchristos long arrival_time;
136e262b48eSchristos long message_size;
137e262b48eSchristos int showq_status;
138e262b48eSchristos int rcpt_count = 0;
13933881f77Schristos int forced_expire;
140e262b48eSchristos
141e262b48eSchristos /*
142e262b48eSchristos * One-time initialization.
143e262b48eSchristos */
144e262b48eSchristos if (queue_name == 0) {
145e262b48eSchristos queue_name = vstring_alloc(100);
146e262b48eSchristos queue_id = vstring_alloc(100);
147e262b48eSchristos addr = vstring_alloc(100);
148e262b48eSchristos why = vstring_alloc(100);
149e262b48eSchristos quote_buf = vstring_alloc(100);
150e262b48eSchristos }
151e262b48eSchristos
152e262b48eSchristos /*
153e262b48eSchristos * Read the message properties and sender address.
154e262b48eSchristos */
155*67b9b338Schristos if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
156*67b9b338Schristos | ATTR_FLAG_PRINTABLE,
157e262b48eSchristos RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name),
158e262b48eSchristos RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
159e262b48eSchristos RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
160e262b48eSchristos RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
16133881f77Schristos RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
162e262b48eSchristos RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
16333881f77Schristos ATTR_TYPE_END) != 6)
164e262b48eSchristos msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
165e262b48eSchristos vstream_printf("{");
166e262b48eSchristos vstream_printf("\"queue_name\": \"%s\", ",
167e262b48eSchristos json_quote(quote_buf, STR(queue_name)));
168e262b48eSchristos vstream_printf("\"queue_id\": \"%s\", ",
169e262b48eSchristos json_quote(quote_buf, STR(queue_id)));
170e262b48eSchristos vstream_printf("\"arrival_time\": %ld, ", arrival_time);
171e262b48eSchristos vstream_printf("\"message_size\": %ld, ", message_size);
17233881f77Schristos vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
173e262b48eSchristos vstream_printf("\"sender\": \"%s\", ",
174e262b48eSchristos json_quote(quote_buf, STR(addr)));
175e262b48eSchristos
176e262b48eSchristos /*
17733881f77Schristos * Read zero or more (recipient, reason) pair(s) until attr_scan_more()
178e262b48eSchristos * consumes a terminator. If the showq daemon messes up, don't try to
179e262b48eSchristos * resynchronize.
180e262b48eSchristos */
181e262b48eSchristos vstream_printf("\"recipients\": [");
182e262b48eSchristos for (rcpt_count = 0; (showq_status = attr_scan_more(showq_stream)) > 0; rcpt_count++) {
183e262b48eSchristos if (rcpt_count > 0)
184e262b48eSchristos vstream_printf(", ");
185e262b48eSchristos vstream_printf("{");
186*67b9b338Schristos if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
187*67b9b338Schristos | ATTR_FLAG_PRINTABLE,
188e262b48eSchristos RECV_ATTR_STR(MAIL_ATTR_RECIP, addr),
189e262b48eSchristos RECV_ATTR_STR(MAIL_ATTR_WHY, why),
190e262b48eSchristos ATTR_TYPE_END) != 2)
191e262b48eSchristos msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
192e262b48eSchristos vstream_printf("\"address\": \"%s\"",
193e262b48eSchristos json_quote(quote_buf, STR(addr)));
194e262b48eSchristos if (LEN(why) > 0)
195e262b48eSchristos vstream_printf(", \"delay_reason\": \"%s\"",
196e262b48eSchristos json_quote(quote_buf, STR(why)));
197e262b48eSchristos vstream_printf("}");
198e262b48eSchristos }
199e262b48eSchristos vstream_printf("]");
200e262b48eSchristos if (showq_status < 0)
201e262b48eSchristos msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
202e262b48eSchristos vstream_printf("}\n");
20333881f77Schristos if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE)
20433881f77Schristos msg_fatal_status(EX_IOERR, "output write error: %m");
205e262b48eSchristos }
206e262b48eSchristos
207e262b48eSchristos /* showq_json - streaming JSON-format output adapter */
208e262b48eSchristos
showq_json(VSTREAM * showq_stream)209e262b48eSchristos void showq_json(VSTREAM *showq_stream)
210e262b48eSchristos {
211e262b48eSchristos int showq_status;
212e262b48eSchristos
213e262b48eSchristos /*
21433881f77Schristos * Emit zero or more queue file objects until attr_scan_more() consumes a
21533881f77Schristos * terminator.
216e262b48eSchristos */
21733881f77Schristos while ((showq_status = attr_scan_more(showq_stream)) > 0
21833881f77Schristos && vstream_ferror(VSTREAM_OUT) == 0) {
219e262b48eSchristos format_json(showq_stream);
220e262b48eSchristos }
221e262b48eSchristos if (showq_status < 0)
222e262b48eSchristos msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
223e262b48eSchristos }
224