xref: /netbsd-src/external/ibm-public/postfix/dist/src/postqueue/showq_json.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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