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 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 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 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