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