1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2022 RackTop Systems, Inc.
14 */
15
16 #include <err.h>
17 #include <errno.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/debug.h>
22 #include <sys/list.h>
23 #include <sys/types.h>
24 #include <sys/sysmacros.h>
25 #include <smbsrv/libsmb.h>
26 #include <ofmt.h>
27 #include <libintl.h>
28 #include <limits.h>
29 #include <locale.h>
30 #include <time.h>
31 #include <upanic.h>
32 #include "smbadm.h"
33
34 /*
35 * Share types for shiX_type fields - duplicated from smb.h
36 * Don't want to pull that in, and these are "carved in stone"
37 * (from the SMB protocol definitions)
38 */
39 #ifndef _SHARE_TYPES_DEFINED_
40 #define _SHARE_TYPES_DEFINED_
41 #define STYPE_DISKTREE 0x00000000
42 #define STYPE_PRINTQ 0x00000001
43 #define STYPE_DEVICE 0x00000002
44 #define STYPE_IPC 0x00000003
45 #define STYPE_MASK 0x0000000F
46 #endif /* _SHARE_TYPES_DEFINED_ */
47
48 #define MINS (60U)
49 #define HRS (60 * MINS)
50 #define DAYS (24 * HRS)
51 #define TIME_FMT "%F %T %Z"
52
53 #define _(x) gettext(x)
54
55 struct flag_tbl {
56 uint32_t flag;
57 const char *name;
58 };
59
60 typedef enum user_field {
61 UF_SESS_ID,
62 UF_DOMAIN,
63 UF_ACCOUNT,
64 UF_USER,
65 UF_UID,
66 UF_WORKSTATION,
67 UF_IP,
68 UF_OS,
69 UF_LOGON_TIME,
70 UF_AGE,
71 UF_NUMOPEN,
72 UF_FLAGS,
73 } user_field_t;
74
75 typedef enum tree_field {
76 TF_ID,
77 TF_TYPE,
78 TF_NUMOPEN,
79 TF_NUMUSERS,
80 TF_TIME,
81 TF_AGE,
82 TF_USERNAME,
83 TF_SHARE,
84 } tree_field_t;
85
86 typedef enum netfileinfo_field {
87 NFIF_FID,
88 NFIF_UNIQID,
89 NFIF_PERMS,
90 NFIF_NUMLOCKS,
91 NFIF_PATH,
92 NFIF_USERNAME,
93 } netfileinfo_field_t;
94
95 static ofmt_handle_t cmd_create_handle(int, char **, const char *,
96 ofmt_field_t *);
97
98 static boolean_t fmt_user(ofmt_arg_t *, char *, uint_t);
99 static boolean_t fmt_tree(ofmt_arg_t *, char *, uint_t);
100 static boolean_t fmt_netfileinfo(ofmt_arg_t *, char *, uint_t);
101
102 static void print_str(const char *restrict, char *restrict, uint_t);
103 static void print_u32(uint32_t, char *, uint_t);
104 static void print_age(time_t, char *, uint_t);
105 static void print_time(time_t, const char *, char *, uint_t);
106 static void print_flags(struct flag_tbl *, size_t, uint32_t, char *, uint_t);
107 static void print_perms(struct flag_tbl *, size_t, uint32_t, char *, uint_t);
108
109 static ofmt_field_t smb_user_fields[] = {
110 { "ID", 4, UF_SESS_ID, fmt_user },
111 { "DOMAIN", 32, UF_DOMAIN, fmt_user },
112 { "ACCT", 16, UF_ACCOUNT, fmt_user },
113 { "USER", 32, UF_USER, fmt_user },
114 { "UID", 12, UF_UID, fmt_user },
115 { "COMPUTER", 16, UF_WORKSTATION, fmt_user },
116 { "IP", 15, UF_IP, fmt_user },
117 { "OS", 8, UF_OS, fmt_user },
118 { "LOGON", 24, UF_LOGON_TIME, fmt_user },
119 { "AGE", 16, UF_AGE, fmt_user },
120 { "NOPEN", 5, UF_NUMOPEN, fmt_user },
121 { "FLAGS", 12, UF_FLAGS, fmt_user },
122 { NULL, 0, 0, NULL }
123 };
124
125 static const char default_user_fields[] = "IP,USER,NOPEN,AGE,FLAGS";
126
127 struct flag_tbl user_flag_tbl[] = {
128 { SMB_ATF_GUEST, "GUEST" },
129 { SMB_ATF_ANON, "ANON" },
130 { SMB_ATF_ADMIN, "ADMIN" },
131 { SMB_ATF_POWERUSER, "POWERUSER" },
132 { SMB_ATF_BACKUPOP, "BACKUPOP" },
133 };
134
135 static ofmt_field_t smb_tree_fields[] = {
136 { "ID", 4, TF_ID, fmt_tree },
137 { "TYPE", 6, TF_TYPE, fmt_tree },
138 { "NOPEN", 6, TF_NUMOPEN, fmt_tree },
139 { "NUSER", 6, TF_NUMUSERS, fmt_tree },
140 { "TIME", 24, TF_TIME, fmt_tree },
141 { "AGE", 12, TF_AGE, fmt_tree },
142 { "USER", 32, TF_USERNAME, fmt_tree },
143 { "SHARE", 16, TF_SHARE, fmt_tree },
144 { NULL, 0, 0, NULL }
145 };
146
147 static const char default_tree_fields[] = "TYPE,SHARE,USER,NOPEN,AGE";
148
149 static ofmt_field_t smb_netfileinfo_fields[] = {
150 { "ID", 4, NFIF_FID, fmt_netfileinfo },
151 { "UNIQID", 8, NFIF_UNIQID, fmt_netfileinfo },
152 { "PERM", 15, NFIF_PERMS, fmt_netfileinfo },
153 { "NLOCK", 6, NFIF_NUMLOCKS, fmt_netfileinfo },
154 { "PATH", 32, NFIF_PATH, fmt_netfileinfo },
155 { "USER", 16, NFIF_USERNAME, fmt_netfileinfo },
156 { NULL, 0, 0, NULL }
157 };
158
159 static const char default_netfileinfo_fields[] = "UNIQID,PATH,USER,NLOCK,PERM";
160
161 /*
162 * Flags are the same as "ls -V" and chmod ACLs:
163 * eg: everyone@:rwxpdDaARWcCos:fd----I:allow
164 * See libsec:acltext.c
165 */
166 static struct flag_tbl nfi_perm_tbl[] = {
167 { ACE_READ_DATA, "r" },
168 { ACE_WRITE_DATA, "w" },
169 { ACE_EXECUTE, "x" },
170 { ACE_APPEND_DATA, "p" },
171 { ACE_DELETE, "d" },
172 { ACE_DELETE_CHILD, "D" },
173 { ACE_READ_ATTRIBUTES, "a" },
174 { ACE_WRITE_ATTRIBUTES, "A" },
175 { ACE_READ_NAMED_ATTRS, "R" },
176 { ACE_WRITE_NAMED_ATTRS, "W" },
177 { ACE_READ_ACL, "c" },
178 { ACE_WRITE_ACL, "C" },
179 { ACE_WRITE_OWNER, "o" },
180 { ACE_SYNCHRONIZE, "s" },
181 };
182
183 static int do_enum(smb_svcenum_t *, ofmt_handle_t);
184 static void ofmt_fatal(ofmt_handle_t, ofmt_field_t *, ofmt_status_t)
185 __NORETURN;
186 static void fatal(const char *, ...) __NORETURN;
187
188 time_t now;
189 boolean_t opt_p;
190 boolean_t opt_x;
191
192 int
cmd_list_sess(int argc,char ** argv)193 cmd_list_sess(int argc, char **argv)
194 {
195 ofmt_handle_t hdl;
196 smb_svcenum_t req = {
197 .se_type = SMB_SVCENUM_TYPE_USER,
198 .se_level = 1,
199 .se_nlimit = UINT32_MAX,
200 };
201 int rc;
202
203 hdl = cmd_create_handle(argc, argv, default_user_fields,
204 smb_user_fields);
205 rc = do_enum(&req, hdl);
206 ofmt_close(hdl);
207 return (rc);
208 }
209
210 int
cmd_list_trees(int argc,char ** argv)211 cmd_list_trees(int argc, char **argv)
212 {
213 ofmt_handle_t hdl;
214 smb_svcenum_t req = {
215 .se_type = SMB_SVCENUM_TYPE_TREE,
216 .se_level = 1,
217 .se_nlimit = UINT32_MAX,
218 };
219 int rc;
220
221 hdl = cmd_create_handle(argc, argv, default_tree_fields,
222 smb_tree_fields);
223 rc = do_enum(&req, hdl);
224 ofmt_close(hdl);
225 return (rc);
226 }
227
228 int
cmd_list_ofiles(int argc,char ** argv)229 cmd_list_ofiles(int argc, char **argv)
230 {
231 ofmt_handle_t hdl;
232 smb_svcenum_t req = {
233 .se_type = SMB_SVCENUM_TYPE_FILE,
234 .se_level = 1,
235 .se_nlimit = UINT32_MAX,
236 };
237 int rc;
238
239 hdl = cmd_create_handle(argc, argv, default_netfileinfo_fields,
240 smb_netfileinfo_fields);
241 rc = do_enum(&req, hdl);
242 ofmt_close(hdl);
243 return (rc);
244 }
245
246 static ofmt_handle_t
cmd_create_handle(int argc,char ** argv,const char * def,ofmt_field_t * templ)247 cmd_create_handle(int argc, char **argv, const char *def, ofmt_field_t *templ)
248 {
249 const char *fields = def;
250 ofmt_handle_t hdl;
251 ofmt_status_t status;
252 uint_t flags = 0;
253 int c;
254
255 while ((c = getopt(argc, argv, "Ho:px")) != -1) {
256 switch (c) {
257 case 'H':
258 flags |= OFMT_NOHEADER;
259 break;
260 case 'o':
261 fields = optarg;
262 break;
263 case 'p':
264 opt_p = B_TRUE;
265 flags |= OFMT_PARSABLE;
266 break;
267 case 'x':
268 opt_x = B_TRUE;
269 break;
270 case '?':
271 /* Note: getopt prints an error for us. */
272 return (NULL);
273 }
274 }
275
276 status = ofmt_open(fields, templ, flags, 0, &hdl);
277 if (status != OFMT_SUCCESS)
278 ofmt_fatal(hdl, templ, status);
279
280 return (hdl);
281 }
282
283 int
cmd_close_ofile(int argc,char ** argv)284 cmd_close_ofile(int argc, char **argv)
285 {
286 uint_t errs = 0;
287
288 if (argc < 2) {
289 warnx(_("Missing file id"));
290 return (2);
291 }
292
293 for (int i = 1; i < argc; i++) {
294 unsigned long ul;
295 int rc;
296
297 errno = 0;
298 ul = strtoul(argv[i], NULL, 0);
299 if (errno != 0) {
300 warnx(_("Invalid file id '%s'"), argv[i]);
301 return (2);
302 }
303 #ifdef _LP64
304 if (ul > UINT32_MAX) {
305 warnx(_("File id %lu too large"), ul);
306 return (2);
307 }
308 #endif
309
310 /*
311 * See SMB_IOC_FILE_CLOSE (ioc.uniqid)
312 * and smb_server_file_close()
313 */
314 rc = smb_kmod_file_close((uint32_t)ul);
315 if (rc != 0) {
316 /*
317 * Since the user can specify the fid as a decimal
318 * or hex value, we use the string they gave us so
319 * the value displayed matches what we were given.
320 */
321 warnc(rc, _("Closing fid %s failed"), argv[i]);
322 errs++;
323 }
324 }
325
326 if (errs > 0)
327 return (1);
328 return (0);
329 }
330
331 int
cmd_close_sess(int argc,char ** argv)332 cmd_close_sess(int argc, char **argv)
333 {
334 const char *client;
335 const char *user = NULL;
336 int rc;
337
338 if (argc < 2) {
339 warnx(_("clientname and username missing"));
340 return (2);
341 }
342 client = argv[1];
343 if (argc > 2) {
344 user = argv[2];
345 }
346
347 /*
348 * See SMB_IOC_SESSION_CLOSE (ioc.client, ioc.username)
349 * and smb_server_session_close(). The "client" part
350 * can be EITHER the "workstation" or the IP address,
351 * as shown in the "COMPUTER" and "IP" fields in the
352 * output of "list-sessions". The (optional) "user"
353 * part is as shown in "USER" part of that output.
354 */
355 rc = smb_kmod_session_close(client, user);
356 if (rc != 0) {
357 rc = 1;
358 }
359 return (rc);
360 }
361
362 static boolean_t
fmt_user(ofmt_arg_t * arg,char * buf,uint_t buflen)363 fmt_user(ofmt_arg_t *arg, char *buf, uint_t buflen)
364 {
365 smb_netuserinfo_t *ui = arg->ofmt_cbarg;
366 user_field_t field = (user_field_t)arg->ofmt_id;
367
368 switch (field) {
369 case UF_SESS_ID:
370 (void) snprintf(buf, buflen, "%" PRIu64, ui->ui_session_id);
371 break;
372 case UF_DOMAIN:
373 print_str(ui->ui_domain, buf, buflen);
374 break;
375 case UF_ACCOUNT:
376 print_str(ui->ui_account, buf, buflen);
377 break;
378 case UF_USER:
379 (void) snprintf(buf, buflen, "%s\\%s", ui->ui_domain,
380 ui->ui_account);
381 break;
382 case UF_UID:
383 VERIFY3U(arg->ofmt_width, <, INT_MAX);
384 (void) snprintf(buf, buflen, "%u", ui->ui_posix_uid);
385 break;
386 case UF_WORKSTATION:
387 print_str(ui->ui_workstation, buf, buflen);
388 break;
389 case UF_IP:
390 (void) smb_inet_ntop(&ui->ui_ipaddr, buf, buflen);
391 break;
392 case UF_OS:
393 /* XXX: Lookup string value */
394 (void) snprintf(buf, buflen, "%" PRId32, ui->ui_native_os);
395 break;
396 case UF_LOGON_TIME:
397 print_time(ui->ui_logon_time, TIME_FMT, buf, buflen);
398 break;
399 case UF_AGE:
400 print_age(now - ui->ui_logon_time, buf, buflen);
401 break;
402 case UF_NUMOPEN:
403 print_u32(ui->ui_numopens, buf, buflen);
404 break;
405 case UF_FLAGS:
406 print_flags(user_flag_tbl, ARRAY_SIZE(user_flag_tbl),
407 ui->ui_flags, buf, buflen);
408 break;
409 default:
410 fatal("%s: invalid field %d", __func__, field);
411 }
412
413 return (B_TRUE);
414 }
415
416 static boolean_t
fmt_tree_type(uint32_t type,char * buf,uint_t buflen)417 fmt_tree_type(uint32_t type, char *buf, uint_t buflen)
418 {
419 switch (type & STYPE_MASK) {
420 case STYPE_DISKTREE:
421 (void) strlcpy(buf, "DISK", buflen);
422 break;
423 case STYPE_PRINTQ:
424 (void) strlcpy(buf, "PRINTQ", buflen);
425 break;
426 case STYPE_DEVICE:
427 (void) strlcpy(buf, "DEVICE", buflen);
428 break;
429 case STYPE_IPC:
430 (void) strlcpy(buf, "IPC", buflen);
431 break;
432 default:
433 (void) snprintf(buf, buflen, "%" PRIx32, type & STYPE_MASK);
434 break;
435 }
436
437 return (B_TRUE);
438 }
439
440 static boolean_t
fmt_tree(ofmt_arg_t * arg,char * buf,uint_t buflen)441 fmt_tree(ofmt_arg_t *arg, char *buf, uint_t buflen)
442 {
443 smb_netconnectinfo_t *nc = arg->ofmt_cbarg;
444 tree_field_t field = (tree_field_t)arg->ofmt_id;
445
446 switch (field) {
447 case TF_ID:
448 (void) snprintf(buf, buflen, "%" PRIu32, nc->ci_id);
449 break;
450 case TF_TYPE:
451 return (fmt_tree_type(nc->ci_type, buf, buflen));
452 case TF_NUMOPEN:
453 print_u32(nc->ci_numopens, buf, buflen);
454 break;
455 case TF_NUMUSERS:
456 print_u32(nc->ci_numusers, buf, buflen);
457 break;
458 case TF_TIME:
459 print_time(now - nc->ci_time, TIME_FMT, buf, buflen);
460 break;
461 case TF_AGE:
462 print_age(nc->ci_time, buf, buflen);
463 break;
464 case TF_USERNAME:
465 print_str(nc->ci_username, buf, buflen);
466 break;
467 case TF_SHARE:
468 print_str(nc->ci_share, buf, buflen);
469 break;
470 default:
471 fatal("%s: invalid field %d", __func__, field);
472 }
473
474 return (B_TRUE);
475 }
476
477 static boolean_t
fmt_netfileinfo(ofmt_arg_t * arg,char * buf,uint_t buflen)478 fmt_netfileinfo(ofmt_arg_t *arg, char *buf, uint_t buflen)
479 {
480 smb_netfileinfo_t *fi = arg->ofmt_cbarg;
481 netfileinfo_field_t field = (netfileinfo_field_t)arg->ofmt_id;
482
483 switch (field) {
484 case NFIF_FID:
485 (void) snprintf(buf, buflen, "%" PRIu16, fi->fi_fid);
486 break;
487 case NFIF_UNIQID:
488 (void) snprintf(buf, buflen, "%" PRIu32, fi->fi_uniqid);
489 break;
490 case NFIF_PERMS:
491 print_perms(nfi_perm_tbl, ARRAY_SIZE(nfi_perm_tbl),
492 fi->fi_permissions, buf, buflen);
493 break;
494 case NFIF_NUMLOCKS:
495 print_u32(fi->fi_numlocks, buf, buflen);
496 break;
497 case NFIF_PATH:
498 print_str(fi->fi_path, buf, buflen);
499 break;
500 case NFIF_USERNAME:
501 print_str(fi->fi_username, buf, buflen);
502 break;
503 default:
504 fatal("%s: invalid field %d", __func__, field);
505 }
506
507 return (B_TRUE);
508 }
509
510 static int
do_enum(smb_svcenum_t * req,ofmt_handle_t hdl)511 do_enum(smb_svcenum_t *req, ofmt_handle_t hdl)
512 {
513 smb_netsvc_t *ns;
514 smb_netsvcitem_t *item;
515 uint32_t n = 0;
516 int rc;
517
518 if (hdl == NULL)
519 return (2); /* exit (2) -- usage */
520 now = time(NULL);
521
522 for (;;) {
523 req->se_nskip = n;
524
525 ns = smb_kmod_enum_init(req);
526 if (ns == NULL) {
527 warnx(_("SMB enum initialization failure"));
528 return (1);
529 }
530
531 rc = smb_kmod_enum(ns);
532 if (rc != 0) {
533 /*
534 * When the SMB service is not running, expect ENXIO.
535 */
536 if (rc == ENXIO) {
537 warnx(_("Kernel SMB server not running"));
538 return (1);
539 }
540 warnc(rc, _("SMB enumeration call failed"));
541 return (1);
542 }
543
544 if (list_is_empty(&ns->ns_list))
545 break;
546
547 for (item = list_head(&ns->ns_list); item != NULL;
548 item = list_next(&ns->ns_list, item)) {
549 ofmt_print(hdl, &item->nsi_un);
550 n++;
551 }
552
553 smb_kmod_enum_fini(ns);
554 }
555 return (0);
556 }
557
558 static void
print_str(const char * restrict src,char * restrict buf,uint_t buflen)559 print_str(const char *restrict src, char *restrict buf, uint_t buflen)
560 {
561 if (src == NULL) {
562 buf[0] = '\0';
563 return;
564 }
565 (void) strlcpy(buf, src, buflen);
566 }
567
568 static void
print_u32(uint32_t val,char * buf,uint_t buflen)569 print_u32(uint32_t val, char *buf, uint_t buflen)
570 {
571 const char *fmt = opt_p ? "%" PRIu32 : "%'" PRIu32;
572
573 (void) snprintf(buf, buflen, fmt, val);
574 }
575
576 static void
print_age(time_t amt,char * buf,uint_t buflen)577 print_age(time_t amt, char *buf, uint_t buflen)
578 {
579 uint32_t days = 0, hours = 0, mins = 0;
580
581 if (opt_p) {
582 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)amt);
583 return;
584 }
585
586 if (amt >= DAYS) {
587 days = amt / DAYS;
588 amt %= DAYS;
589 }
590 if (amt >= HRS) {
591 hours = amt / HRS;
592 amt %= HRS;
593 }
594 if (amt >= MINS) {
595 mins = amt / MINS;
596 amt %= MINS;
597 }
598
599 if (days > 0) {
600 int n = snprintf(buf, buflen, "%" PRIu32 " days%s",
601 days, (hours > 0 || mins > 0 || amt > 0) ? ", " : "");
602
603 VERIFY3U(buflen, >, n);
604
605 buf += n;
606 buflen -= n;
607 }
608
609 (void) snprintf(buf, buflen, "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32,
610 hours, mins, amt);
611 }
612
613 static void
print_time(time_t when,const char * fmt,char * buf,uint_t buflen)614 print_time(time_t when, const char *fmt, char *buf, uint_t buflen)
615 {
616 const struct tm *tm;
617
618 if (opt_p) {
619 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)when);
620 return;
621 }
622
623 tm = localtime(&when);
624 (void) strftime(buf, buflen - 1, fmt, tm);
625 }
626
627 static void
print_flags(struct flag_tbl * tbl,size_t nent,uint32_t val,char * buf,uint_t buflen)628 print_flags(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf,
629 uint_t buflen)
630 {
631 uint_t n = 0;
632 uint_t i;
633
634 if (opt_x) {
635 (void) snprintf(buf, buflen, "%" PRIx32, val);
636 return;
637 }
638
639 for (i = 0; i < nent; i++) {
640 if ((val & tbl[i].flag) == 0)
641 continue;
642 if (n > 0)
643 (void) strlcat(buf, ",", buflen);
644 (void) strlcat(buf, tbl[i].name, buflen);
645 n++;
646 }
647
648 if (n == 0)
649 (void) strlcat(buf, "-", buflen);
650 }
651
652 static void
print_perms(struct flag_tbl * tbl,size_t nent,uint32_t val,char * buf,uint_t buflen)653 print_perms(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf,
654 uint_t buflen)
655 {
656 uint_t n = 0;
657 uint_t i;
658
659 if (opt_x) {
660 (void) snprintf(buf, buflen, "%" PRIx32, val);
661 return;
662 }
663
664 for (i = 0; i < nent; i++) {
665 if ((val & tbl[i].flag) == 0) {
666 (void) strlcat(buf, "-", buflen);
667 } else {
668 (void) strlcat(buf, tbl[i].name, buflen);
669 }
670 n++;
671 }
672 }
673
674 __NORETURN static void
ofmt_fatal(ofmt_handle_t hdl,ofmt_field_t * templ,ofmt_status_t status)675 ofmt_fatal(ofmt_handle_t hdl, ofmt_field_t *templ, ofmt_status_t status)
676 {
677 char buf[OFMT_BUFSIZE];
678 char *msg = ofmt_strerror(hdl, status, buf, sizeof (buf));
679
680 warnx(_("ofmt error: %s"), msg);
681
682 if (status == OFMT_EBADFIELDS ||
683 status == OFMT_ENOFIELDS) {
684 ofmt_field_t *f = templ;
685 fprintf(stderr, _("Valid fields are: "));
686 while (f->of_name != NULL) {
687 fprintf(stderr, "%s", f->of_name);
688 f++;
689 if (f->of_name != NULL)
690 fprintf(stderr, ",");
691 }
692 fprintf(stderr, "\n");
693 }
694
695 exit(EXIT_FAILURE);
696 }
697
698 __NORETURN static void
fatal(const char * msg,...)699 fatal(const char *msg, ...)
700 {
701 char buf[128];
702 va_list ap;
703 size_t len;
704
705 va_start(ap, msg);
706 (void) vsnprintf(buf, sizeof (buf), msg, ap);
707 va_end(ap);
708
709 len = strlen(buf);
710 upanic(buf, len);
711 }
712