1*7f2fe78bSCy Schubert /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2*7f2fe78bSCy Schubert /* kdc/ndr.c - NDR encoding and decoding functions */
3*7f2fe78bSCy Schubert /*
4*7f2fe78bSCy Schubert * Copyright (C) 2021 by Red Hat, Inc.
5*7f2fe78bSCy Schubert * All rights reserved.
6*7f2fe78bSCy Schubert *
7*7f2fe78bSCy Schubert * Redistribution and use in source and binary forms, with or without
8*7f2fe78bSCy Schubert * modification, are permitted provided that the following conditions
9*7f2fe78bSCy Schubert * are met:
10*7f2fe78bSCy Schubert *
11*7f2fe78bSCy Schubert * * Redistributions of source code must retain the above copyright
12*7f2fe78bSCy Schubert * notice, this list of conditions and the following disclaimer.
13*7f2fe78bSCy Schubert *
14*7f2fe78bSCy Schubert * * Redistributions in binary form must reproduce the above copyright
15*7f2fe78bSCy Schubert * notice, this list of conditions and the following disclaimer in
16*7f2fe78bSCy Schubert * the documentation and/or other materials provided with the
17*7f2fe78bSCy Schubert * distribution.
18*7f2fe78bSCy Schubert *
19*7f2fe78bSCy Schubert * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20*7f2fe78bSCy Schubert * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21*7f2fe78bSCy Schubert * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22*7f2fe78bSCy Schubert * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23*7f2fe78bSCy Schubert * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24*7f2fe78bSCy Schubert * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25*7f2fe78bSCy Schubert * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26*7f2fe78bSCy Schubert * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27*7f2fe78bSCy Schubert * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28*7f2fe78bSCy Schubert * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29*7f2fe78bSCy Schubert * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30*7f2fe78bSCy Schubert * OF THE POSSIBILITY OF SUCH DAMAGE.
31*7f2fe78bSCy Schubert */
32*7f2fe78bSCy Schubert
33*7f2fe78bSCy Schubert #include "k5-int.h"
34*7f2fe78bSCy Schubert #include "k5-input.h"
35*7f2fe78bSCy Schubert #include "k5-buf.h"
36*7f2fe78bSCy Schubert #include "k5-utf8.h"
37*7f2fe78bSCy Schubert #include "kdc_util.h"
38*7f2fe78bSCy Schubert
39*7f2fe78bSCy Schubert struct encoded_wchars {
40*7f2fe78bSCy Schubert uint16_t bytes_len;
41*7f2fe78bSCy Schubert uint16_t num_wchars;
42*7f2fe78bSCy Schubert uint8_t *encoded;
43*7f2fe78bSCy Schubert };
44*7f2fe78bSCy Schubert
45*7f2fe78bSCy Schubert /*
46*7f2fe78bSCy Schubert * MS-DTYP 2.3.10:
47*7f2fe78bSCy Schubert *
48*7f2fe78bSCy Schubert * typedef struct _RPC_UNICODE_STRING {
49*7f2fe78bSCy Schubert * unsigned short Length;
50*7f2fe78bSCy Schubert * unsigned short MaximumLength;
51*7f2fe78bSCy Schubert * [size_is(MaximumLength/2), length_is(Length/2)] WCHAR* Buffer;
52*7f2fe78bSCy Schubert * } RPC_UNICODE_STRING, *PRPC_UNICODE_STRING;
53*7f2fe78bSCy Schubert *
54*7f2fe78bSCy Schubert * Note that Buffer is not a String - there's no termination.
55*7f2fe78bSCy Schubert *
56*7f2fe78bSCy Schubert * We don't actually decode Length and MaximumLength here - this is a
57*7f2fe78bSCy Schubert * conformant-varying array, which means that (per DCE-1.1-RPC 14.3.7.2) where
58*7f2fe78bSCy Schubert * those actually appear in the serialized data is variable depending on
59*7f2fe78bSCy Schubert * whether the string is at top level of the struct or not. (This also
60*7f2fe78bSCy Schubert * affects where the pointer identifier appears.)
61*7f2fe78bSCy Schubert *
62*7f2fe78bSCy Schubert * See MS-RPCE 4.7 for what an RPC_UNICODE_STRING looks like when not at
63*7f2fe78bSCy Schubert * top-level.
64*7f2fe78bSCy Schubert */
65*7f2fe78bSCy Schubert static krb5_error_code
dec_wchar_pointer(struct k5input * in,char ** out)66*7f2fe78bSCy Schubert dec_wchar_pointer(struct k5input *in, char **out)
67*7f2fe78bSCy Schubert {
68*7f2fe78bSCy Schubert const uint8_t *bytes;
69*7f2fe78bSCy Schubert uint32_t actual_count;
70*7f2fe78bSCy Schubert
71*7f2fe78bSCy Schubert /* Maximum count. */
72*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(in);
73*7f2fe78bSCy Schubert /* Offset - all zeroes, "should" not be checked. */
74*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(in);
75*7f2fe78bSCy Schubert
76*7f2fe78bSCy Schubert actual_count = k5_input_get_uint32_le(in);
77*7f2fe78bSCy Schubert if (actual_count > UINT32_MAX / 2)
78*7f2fe78bSCy Schubert return ERANGE;
79*7f2fe78bSCy Schubert
80*7f2fe78bSCy Schubert bytes = k5_input_get_bytes(in, actual_count * 2);
81*7f2fe78bSCy Schubert if (bytes == NULL || k5_utf16le_to_utf8(bytes, actual_count * 2, out) != 0)
82*7f2fe78bSCy Schubert return EINVAL;
83*7f2fe78bSCy Schubert
84*7f2fe78bSCy Schubert /* Always align on 4. */
85*7f2fe78bSCy Schubert if (actual_count % 2 == 1)
86*7f2fe78bSCy Schubert (void)k5_input_get_uint16_le(in);
87*7f2fe78bSCy Schubert
88*7f2fe78bSCy Schubert return 0;
89*7f2fe78bSCy Schubert }
90*7f2fe78bSCy Schubert
91*7f2fe78bSCy Schubert static krb5_error_code
enc_wchar_pointer(const char * utf8,struct encoded_wchars * encoded_out)92*7f2fe78bSCy Schubert enc_wchar_pointer(const char *utf8, struct encoded_wchars *encoded_out)
93*7f2fe78bSCy Schubert {
94*7f2fe78bSCy Schubert krb5_error_code ret;
95*7f2fe78bSCy Schubert struct k5buf b;
96*7f2fe78bSCy Schubert size_t utf16len, num_wchars;
97*7f2fe78bSCy Schubert uint8_t *utf16;
98*7f2fe78bSCy Schubert
99*7f2fe78bSCy Schubert ret = k5_utf8_to_utf16le(utf8, &utf16, &utf16len);
100*7f2fe78bSCy Schubert if (ret)
101*7f2fe78bSCy Schubert return ret;
102*7f2fe78bSCy Schubert
103*7f2fe78bSCy Schubert num_wchars = utf16len / 2;
104*7f2fe78bSCy Schubert
105*7f2fe78bSCy Schubert k5_buf_init_dynamic(&b);
106*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, num_wchars + 1);
107*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, 0);
108*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, num_wchars);
109*7f2fe78bSCy Schubert k5_buf_add_len(&b, utf16, utf16len);
110*7f2fe78bSCy Schubert
111*7f2fe78bSCy Schubert free(utf16);
112*7f2fe78bSCy Schubert
113*7f2fe78bSCy Schubert if (num_wchars % 2 == 1)
114*7f2fe78bSCy Schubert k5_buf_add_uint16_le(&b, 0);
115*7f2fe78bSCy Schubert
116*7f2fe78bSCy Schubert ret = k5_buf_status(&b);
117*7f2fe78bSCy Schubert if (ret)
118*7f2fe78bSCy Schubert return ret;
119*7f2fe78bSCy Schubert
120*7f2fe78bSCy Schubert encoded_out->bytes_len = b.len;
121*7f2fe78bSCy Schubert encoded_out->num_wchars = num_wchars;
122*7f2fe78bSCy Schubert encoded_out->encoded = b.data;
123*7f2fe78bSCy Schubert return 0;
124*7f2fe78bSCy Schubert }
125*7f2fe78bSCy Schubert
126*7f2fe78bSCy Schubert /*
127*7f2fe78bSCy Schubert * Decode a delegation info structure, leaving room to add an additional
128*7f2fe78bSCy Schubert * service.
129*7f2fe78bSCy Schubert *
130*7f2fe78bSCy Schubert * MS-PAC 2.9:
131*7f2fe78bSCy Schubert *
132*7f2fe78bSCy Schubert * typedef struct _S4U_DELEGATION_INFO {
133*7f2fe78bSCy Schubert * RPC_UNICODE_STRING S4U2proxyTarget;
134*7f2fe78bSCy Schubert * ULONG TransitedListSize;
135*7f2fe78bSCy Schubert * [size_is(TransitedListSize)] PRPC_UNICODE_STRING S4UTransitedServices;
136*7f2fe78bSCy Schubert * } S4U_DELEGATION_INFO, *PS4U_DELEGATION_INFO;
137*7f2fe78bSCy Schubert */
138*7f2fe78bSCy Schubert krb5_error_code
ndr_dec_delegation_info(krb5_data * data,struct pac_s4u_delegation_info ** out)139*7f2fe78bSCy Schubert ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
140*7f2fe78bSCy Schubert {
141*7f2fe78bSCy Schubert krb5_error_code ret;
142*7f2fe78bSCy Schubert struct pac_s4u_delegation_info *di = NULL;
143*7f2fe78bSCy Schubert struct k5input in;
144*7f2fe78bSCy Schubert uint32_t i, object_buffer_length, nservices;
145*7f2fe78bSCy Schubert uint8_t version, endianness, common_header_length;
146*7f2fe78bSCy Schubert
147*7f2fe78bSCy Schubert *out = NULL;
148*7f2fe78bSCy Schubert
149*7f2fe78bSCy Schubert di = k5alloc(sizeof(*di), &ret);
150*7f2fe78bSCy Schubert if (di == NULL)
151*7f2fe78bSCy Schubert return ret;
152*7f2fe78bSCy Schubert
153*7f2fe78bSCy Schubert k5_input_init(&in, data->data, data->length);
154*7f2fe78bSCy Schubert
155*7f2fe78bSCy Schubert /* Common Type Header - MS-RPCE 2.2.6.1 */
156*7f2fe78bSCy Schubert version = k5_input_get_byte(&in);
157*7f2fe78bSCy Schubert endianness = k5_input_get_byte(&in);
158*7f2fe78bSCy Schubert common_header_length = k5_input_get_uint16_le(&in);
159*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in); /* Filler - 0xcccccccc. */
160*7f2fe78bSCy Schubert if (version != 1 || endianness != 0x10 || common_header_length != 8) {
161*7f2fe78bSCy Schubert ret = EINVAL;
162*7f2fe78bSCy Schubert goto error;
163*7f2fe78bSCy Schubert }
164*7f2fe78bSCy Schubert
165*7f2fe78bSCy Schubert /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
166*7f2fe78bSCy Schubert object_buffer_length = k5_input_get_uint32_le(&in);
167*7f2fe78bSCy Schubert if (data->length < 16 || object_buffer_length != data->length - 16) {
168*7f2fe78bSCy Schubert ret = EINVAL;
169*7f2fe78bSCy Schubert goto error;
170*7f2fe78bSCy Schubert }
171*7f2fe78bSCy Schubert
172*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in); /* Filler - 0. */
173*7f2fe78bSCy Schubert
174*7f2fe78bSCy Schubert /* This code doesn't handle re-used pointers, which could come into play in
175*7f2fe78bSCy Schubert * the unlikely case of a delegation loop. */
176*7f2fe78bSCy Schubert
177*7f2fe78bSCy Schubert /* Pointer. Microsoft always starts at 00 00 02 00 */
178*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in);
179*7f2fe78bSCy Schubert /* Length of proxy target - 2 */
180*7f2fe78bSCy Schubert (void)k5_input_get_uint16_le(&in);
181*7f2fe78bSCy Schubert /* Length of proxy target */
182*7f2fe78bSCy Schubert (void)k5_input_get_uint16_le(&in);
183*7f2fe78bSCy Schubert /* Another pointer - 04 00 02 00. Microsoft increments by 4 (le). */
184*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in);
185*7f2fe78bSCy Schubert
186*7f2fe78bSCy Schubert /* Transited services length - header version. */
187*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in);
188*7f2fe78bSCy Schubert
189*7f2fe78bSCy Schubert /* More pointer: 08 00 02 00 */
190*7f2fe78bSCy Schubert (void)k5_input_get_uint32_le(&in);
191*7f2fe78bSCy Schubert
192*7f2fe78bSCy Schubert ret = dec_wchar_pointer(&in, &di->proxy_target);
193*7f2fe78bSCy Schubert if (ret)
194*7f2fe78bSCy Schubert goto error;
195*7f2fe78bSCy Schubert nservices = k5_input_get_uint32_le(&in);
196*7f2fe78bSCy Schubert
197*7f2fe78bSCy Schubert /* Here, we have encoded 2 bytes of length, 2 bytes of (length + 2), and 4
198*7f2fe78bSCy Schubert * bytes of pointer, for each element (deferred pointers). */
199*7f2fe78bSCy Schubert if (nservices > data->length / 8) {
200*7f2fe78bSCy Schubert ret = ERANGE;
201*7f2fe78bSCy Schubert goto error;
202*7f2fe78bSCy Schubert }
203*7f2fe78bSCy Schubert (void)k5_input_get_bytes(&in, 8 * nservices);
204*7f2fe78bSCy Schubert
205*7f2fe78bSCy Schubert /* Since we're likely to add another entry, leave a blank at the end. */
206*7f2fe78bSCy Schubert di->transited_services = k5calloc(nservices + 1, sizeof(char *), &ret);
207*7f2fe78bSCy Schubert if (di->transited_services == NULL)
208*7f2fe78bSCy Schubert goto error;
209*7f2fe78bSCy Schubert
210*7f2fe78bSCy Schubert for (i = 0; i < nservices; i++) {
211*7f2fe78bSCy Schubert ret = dec_wchar_pointer(&in, &di->transited_services[i]);
212*7f2fe78bSCy Schubert if (ret)
213*7f2fe78bSCy Schubert goto error;
214*7f2fe78bSCy Schubert di->transited_services_length++;
215*7f2fe78bSCy Schubert }
216*7f2fe78bSCy Schubert
217*7f2fe78bSCy Schubert ret = in.status;
218*7f2fe78bSCy Schubert if (ret)
219*7f2fe78bSCy Schubert goto error;
220*7f2fe78bSCy Schubert
221*7f2fe78bSCy Schubert *out = di;
222*7f2fe78bSCy Schubert return 0;
223*7f2fe78bSCy Schubert
224*7f2fe78bSCy Schubert error:
225*7f2fe78bSCy Schubert ndr_free_delegation_info(di);
226*7f2fe78bSCy Schubert return ret;
227*7f2fe78bSCy Schubert }
228*7f2fe78bSCy Schubert
229*7f2fe78bSCy Schubert /* Empirically, Microsoft starts pointers at 00 00 02 00, and if treated little
230*7f2fe78bSCy Schubert * endian, they increase by 4. */
231*7f2fe78bSCy Schubert static inline void
write_ptr(struct k5buf * buf,uint32_t * pointer)232*7f2fe78bSCy Schubert write_ptr(struct k5buf *buf, uint32_t *pointer)
233*7f2fe78bSCy Schubert {
234*7f2fe78bSCy Schubert if (*pointer == 0)
235*7f2fe78bSCy Schubert *pointer = 0x00020000;
236*7f2fe78bSCy Schubert k5_buf_add_uint32_le(buf, *pointer);
237*7f2fe78bSCy Schubert *pointer += 4;
238*7f2fe78bSCy Schubert }
239*7f2fe78bSCy Schubert
240*7f2fe78bSCy Schubert krb5_error_code
ndr_enc_delegation_info(struct pac_s4u_delegation_info * in,krb5_data * out)241*7f2fe78bSCy Schubert ndr_enc_delegation_info(struct pac_s4u_delegation_info *in, krb5_data *out)
242*7f2fe78bSCy Schubert {
243*7f2fe78bSCy Schubert krb5_error_code ret;
244*7f2fe78bSCy Schubert size_t i;
245*7f2fe78bSCy Schubert struct k5buf b;
246*7f2fe78bSCy Schubert struct encoded_wchars pt_encoded = { 0 }, *tss_encoded = NULL;
247*7f2fe78bSCy Schubert uint32_t pointer = 0;
248*7f2fe78bSCy Schubert
249*7f2fe78bSCy Schubert /* Encode ahead of time since we need the lengths. */
250*7f2fe78bSCy Schubert ret = enc_wchar_pointer(in->proxy_target, &pt_encoded);
251*7f2fe78bSCy Schubert if (ret)
252*7f2fe78bSCy Schubert goto cleanup;
253*7f2fe78bSCy Schubert
254*7f2fe78bSCy Schubert tss_encoded = k5calloc(in->transited_services_length, sizeof(*tss_encoded),
255*7f2fe78bSCy Schubert &ret);
256*7f2fe78bSCy Schubert if (tss_encoded == NULL)
257*7f2fe78bSCy Schubert goto cleanup;
258*7f2fe78bSCy Schubert
259*7f2fe78bSCy Schubert k5_buf_init_dynamic(&b);
260*7f2fe78bSCy Schubert
261*7f2fe78bSCy Schubert /* Common Type Header - MS-RPCE 2.2.6.1 */
262*7f2fe78bSCy Schubert k5_buf_add_len(&b, "\x01\x10\x08\x00", 4);
263*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, 0xcccccccc);
264*7f2fe78bSCy Schubert
265*7f2fe78bSCy Schubert /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
266*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, 0); /* Skip over where payload length goes. */
267*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, 0); /* Filler - all zeroes. */
268*7f2fe78bSCy Schubert
269*7f2fe78bSCy Schubert write_ptr(&b, &pointer);
270*7f2fe78bSCy Schubert k5_buf_add_uint16_le(&b, 2 * pt_encoded.num_wchars);
271*7f2fe78bSCy Schubert k5_buf_add_uint16_le(&b, 2 * (pt_encoded.num_wchars + 1));
272*7f2fe78bSCy Schubert write_ptr(&b, &pointer);
273*7f2fe78bSCy Schubert
274*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, in->transited_services_length);
275*7f2fe78bSCy Schubert write_ptr(&b, &pointer);
276*7f2fe78bSCy Schubert
277*7f2fe78bSCy Schubert k5_buf_add_len(&b, pt_encoded.encoded, pt_encoded.bytes_len);
278*7f2fe78bSCy Schubert
279*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, in->transited_services_length);
280*7f2fe78bSCy Schubert
281*7f2fe78bSCy Schubert /* Deferred pointers. */
282*7f2fe78bSCy Schubert for (i = 0; i < in->transited_services_length; i++) {
283*7f2fe78bSCy Schubert ret = enc_wchar_pointer(in->transited_services[i], &tss_encoded[i]);
284*7f2fe78bSCy Schubert if (ret)
285*7f2fe78bSCy Schubert goto cleanup;
286*7f2fe78bSCy Schubert
287*7f2fe78bSCy Schubert k5_buf_add_uint16_le(&b, 2 * tss_encoded[i].num_wchars);
288*7f2fe78bSCy Schubert k5_buf_add_uint16_le(&b, 2 * (tss_encoded[i].num_wchars + 1));
289*7f2fe78bSCy Schubert write_ptr(&b, &pointer);
290*7f2fe78bSCy Schubert }
291*7f2fe78bSCy Schubert
292*7f2fe78bSCy Schubert for (i = 0; i < in->transited_services_length; i++)
293*7f2fe78bSCy Schubert k5_buf_add_len(&b, tss_encoded[i].encoded, tss_encoded[i].bytes_len);
294*7f2fe78bSCy Schubert
295*7f2fe78bSCy Schubert /* Now, pad to 8 bytes. RPC_UNICODE_STRING is aligned on 4 bytes. */
296*7f2fe78bSCy Schubert if (b.len % 8 != 0)
297*7f2fe78bSCy Schubert k5_buf_add_uint32_le(&b, 0);
298*7f2fe78bSCy Schubert
299*7f2fe78bSCy Schubert /* Record the payload length where we skipped over it previously. */
300*7f2fe78bSCy Schubert if (b.data != NULL)
301*7f2fe78bSCy Schubert store_32_le(b.len - 0x10, ((uint8_t *)b.data) + 8);
302*7f2fe78bSCy Schubert
303*7f2fe78bSCy Schubert ret = k5_buf_status(&b);
304*7f2fe78bSCy Schubert if (ret)
305*7f2fe78bSCy Schubert goto cleanup;
306*7f2fe78bSCy Schubert
307*7f2fe78bSCy Schubert *out = make_data(b.data, b.len);
308*7f2fe78bSCy Schubert b.data = NULL;
309*7f2fe78bSCy Schubert
310*7f2fe78bSCy Schubert cleanup:
311*7f2fe78bSCy Schubert free(b.data);
312*7f2fe78bSCy Schubert free(pt_encoded.encoded);
313*7f2fe78bSCy Schubert for (i = 0; tss_encoded != NULL && i < in->transited_services_length; i++)
314*7f2fe78bSCy Schubert free(tss_encoded[i].encoded);
315*7f2fe78bSCy Schubert free(tss_encoded);
316*7f2fe78bSCy Schubert return ret;
317*7f2fe78bSCy Schubert }
318*7f2fe78bSCy Schubert
319*7f2fe78bSCy Schubert void
ndr_free_delegation_info(struct pac_s4u_delegation_info * di)320*7f2fe78bSCy Schubert ndr_free_delegation_info(struct pac_s4u_delegation_info *di)
321*7f2fe78bSCy Schubert {
322*7f2fe78bSCy Schubert uint32_t i;
323*7f2fe78bSCy Schubert
324*7f2fe78bSCy Schubert if (di == NULL)
325*7f2fe78bSCy Schubert return;
326*7f2fe78bSCy Schubert free(di->proxy_target);
327*7f2fe78bSCy Schubert for (i = 0; i < di->transited_services_length; i++)
328*7f2fe78bSCy Schubert free(di->transited_services[i]);
329*7f2fe78bSCy Schubert free(di->transited_services);
330*7f2fe78bSCy Schubert free(di);
331*7f2fe78bSCy Schubert }
332