• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * lws-minimal-secure-streams-server
3  *
4  * Written in 2010-2020 by Andy Green <andy@warmcat.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  */
9 
10 #include <libwebsockets.h>
11 #include <string.h>
12 #include <signal.h>
13 
14 extern const lws_ss_info_t ssi_client, ssi_server;
15 
16 static struct lws_context *context;
17 int interrupted, bad = 1, multipart;
18 static const char * const default_ss_policy =
19 	"{"
20 	  "\"release\":"			"\"01234567\","
21 	  "\"product\":"			"\"myproduct\","
22 	  "\"schema-version\":"			"1,"
23 	  "\"retry\": ["	/* named backoff / retry strategies */
24 		"{\"default\": {"
25 			"\"backoff\": ["	 "1000,"
26 						 "2000,"
27 						 "3000,"
28 						 "5000,"
29 						"10000"
30 				"],"
31 			"\"conceal\":"		"5,"
32 			"\"jitterpc\":"		"20,"
33 			"\"svalidping\":"	"300,"
34 			"\"svalidhup\":"	"310"
35 		"}}"
36 	  "],"
37 	  "\"certs\": [" /* named individual certificates in BASE64 DER */
38 		/*
39 		 * Need to be in order from root cert... notice sometimes as
40 		 * with Let's Encrypt there are multiple possible validation
41 		 * paths, all the pieces for one validation path must be
42 		 * given, excluding the server cert itself.  Let's Encrypt
43 		 * intermediate is signed by their ISRG Root CA but also is
44 		 * cross-signed by an IdenTrust intermediate that's widely
45 		 * deployed in browsers.  We use the ISRG path because that
46 		 * way we can skip the extra IdenTrust root cert.
47 		 */
48 			"{\"isrg_root_x1\": \""
49 	"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
50 	"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
51 	"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
52 	"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
53 	"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
54 	"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
55 	"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
56 	"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
57 	"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
58 	"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
59 	"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
60 	"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
61 	"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
62 	"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
63 	"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
64 	"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
65 	"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
66 	"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
67 	"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
68 	"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
69 	"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
70 	"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
71 	"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
72 	"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
73 	"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
74 	"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
75 	"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
76 	"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
77 	"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
78 	  "\"},"
79 		/*
80 		 * a selfsigned cert for localhost for 100 years
81 		 */
82 		"{\"self_localhost\": \""
83 	"MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD"
84 	"VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb"
85 	"MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx"
86 	"HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3"
87 	"WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl"
88 	"d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0"
89 	"cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA"
90 	"aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW"
91 	"aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8"
92 	"Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek"
93 	"LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH"
94 	"KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6"
95 	"jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ"
96 	"Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz"
97 	"TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK"
98 	"Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0"
99 	"nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo"
100 	"GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p"
101 	"sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU"
102 	"9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar"
103 	"jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow"
104 	"YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA"
105 	"xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P"
106 	"wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34"
107 	"H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv"
108 	"xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk"
109 	"ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g"
110 	"1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA"
111 	"AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg"
112 	"mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s"
113 	"8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX"
114 	"e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE="
115 		"\"},"
116 		/*
117 		 * the private key for above
118 		 */
119 		"{\"self_localhost_key\": \""
120 	"MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ"
121 	"PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK"
122 	"nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ"
123 	"toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU"
124 	"0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT"
125 	"J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS"
126 	"Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN"
127 	"uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9"
128 	"fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn"
129 	"zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au"
130 	"ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB"
131 	"QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f"
132 	"qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+"
133 	"vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9"
134 	"fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A"
135 	"Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT"
136 	"G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/"
137 	"HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8"
138 	"YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl"
139 	"xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs"
140 	"esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw"
141 	"zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz"
142 	"mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw"
143 	"au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77"
144 	"40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5"
145 	"YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH"
146 	"PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj"
147 	"W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR"
148 	"naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6"
149 	"2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m"
150 	"39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79"
151 	"J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC"
152 	"R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp"
153 	"Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh"
154 	"BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE"
155 	"fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ"
156 	"x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI"
157 	"UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM"
158 	"OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L"
159 	"65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A"
160 	"aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5"
161 	"SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S"
162 	"me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I"
163 	"G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK"
164 	"TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY"
165 	"56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2"
166 	"gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr"
167 	"Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E"
168 	"NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs"
169 	"fBrpEY1IATtPq1taBZZogRqI3rOkkPk="
170 		"\"}"
171 	  "],"
172 	  "\"trust_stores\": [" /* named cert chains */
173 		"{"
174 			"\"name\": \"le_via_isrg\","
175 			"\"stack\": ["
176 				"\"isrg_root_x1\""
177 			"]"
178 		"}"
179 	  "],"
180 	  "\"s\": ["
181 		/*
182 		 * Client streamtypes
183 		 */
184 
185 		"{\"mintest\": {"
186 			"\"endpoint\":"		"\"warmcat.com\","
187 			"\"port\":"		"443,"
188 			"\"protocol\":"		"\"h2\","
189 			"\"http_method\":"	"\"GET\","
190 			"\"http_url\":"		"\"index.html\","
191 			"\"tls\":"		"true,"
192 			"\"retry\":"		"\"default\","
193 			"\"tls_trust_store\":"	"\"le_via_isrg\""
194 		"}},"
195 
196 		/*
197 		 * This streamtype represents an h2 server listening on :7681,
198 		 * using a 100-y self-signed tls cert
199 		 */
200 
201 		"{\"myserver\": {"
202 			/* if given, "endpoint" is network if to bind to */
203 			"\"server\":"		"true,"
204 			"\"port\":"		"7681,"
205 			"\"protocol\":"		"\"h1\","
206 			"\"metadata\": [{"
207 				"\"mime\": \"Content-Type:\","
208 				"\"method\": \"\","
209 				"\"path\": \"\""
210 			"}],"
211 			"\"tls\":"		"true,"
212 			/*
213 			 * A ws server is an http server, if you give a
214 			 * ws_subprotocol here it's understood we also serve
215 			 * that ove ws or wss according to tls
216 			 */
217 			"\"ws_subprotocol\":"	"\"mywsprotocol\","
218 			"\"server_cert\":"	"\"self_localhost\","
219 			"\"server_key\":"	"\"self_localhost_key\""
220 		"}},"
221 
222 	  "]"
223 	"}"
224 ;
225 
226 static int
smd_cb(void * opaque,lws_smd_class_t c,lws_usec_t ts,void * buf,size_t len)227 smd_cb(void *opaque, lws_smd_class_t c, lws_usec_t ts, void *buf, size_t len)
228 {
229 	if ((c & LWSSMDCL_SYSTEM_STATE) &&
230 	    !lws_json_simple_strcmp(buf, len, "\"state\":", "OPERATIONAL")) {
231 
232 		/* create the secure streams */
233 
234 		lwsl_notice("%s: creating server stream\n", __func__);
235 
236 		if (lws_ss_create(context, 0, &ssi_server, NULL, NULL,
237 				  NULL, NULL)) {
238 			lwsl_err("%s: failed to create secure stream\n",
239 				 __func__);
240 			bad = 1;
241 			interrupted = 1;
242 			lws_cancel_service(context);
243 			return -1;
244 		}
245 #if 0
246 		lwsl_notice("%s: creating client stream\n", __func__);
247 
248 		if (lws_ss_create(context, 0, &ssi_client, NULL, NULL,
249 				  NULL, NULL)) {
250 			lwsl_err("%s: failed to create secure stream\n",
251 				 __func__);
252 			return -1;
253 		}
254 #endif
255 	}
256 
257 	return 0;
258 }
259 
260 static void
sigint_handler(int sig)261 sigint_handler(int sig)
262 {
263 	interrupted = 1;
264 }
265 
main(int argc,const char ** argv)266 int main(int argc, const char **argv)
267 {
268 	struct lws_context_creation_info info;
269 	int n = 0;
270 
271 	signal(SIGINT, sigint_handler);
272 
273 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
274 	lws_cmdline_option_handle_builtin(argc, argv, &info);
275 
276 	if (lws_cmdline_option(argc, argv, "-m"))
277 		multipart = 1;
278 
279 	lwsl_user("LWS Secure Streams Server\n");
280 
281 	info.options			= LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
282 					  LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
283 	info.fd_limit_per_thread	= 1 + 6 + 1;
284 	info.pss_policies_json		= default_ss_policy;
285 	info.port			= CONTEXT_PORT_NO_LISTEN;
286 	info.early_smd_cb		= smd_cb;
287 	info.early_smd_class_filter	= LWSSMDCL_SYSTEM_STATE;
288 
289 	context = lws_create_context(&info);
290 	if (!context) {
291 		lwsl_err("lws init failed\n");
292 		return 1;
293 	}
294 
295 	/* the event loop */
296 
297 	while (n >= 0 && !interrupted)
298 		n = lws_service(context, 0);
299 
300 	bad = 0;
301 
302 	lws_context_destroy(context);
303 	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
304 
305 	return bad;
306 }
307