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