1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Public utilities and convenience calls (from the API spec): 29 * SLPFindScopes (queries for all known scopes) 30 * SLPEscape / Unescape 31 * SLPFree 32 * SLPSet/GetProperty 33 */ 34 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <syslog.h> 39 #include <netdb.h> 40 #include <unistd.h> 41 #include <libintl.h> 42 #include <slp-internal.h> 43 44 struct scopes_tree { 45 void *scopes; 46 int len; 47 }; 48 49 typedef SLPBoolean SLPScopeCallback(SLPHandle, const char *, SLPError, void *); 50 51 static SLPSrvURLCallback collate_scopes; 52 static void collect_scopes(void *, VISIT, int, void *); 53 static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *, char *, 54 SLPScopeCallback, void *, 55 void **, int *); 56 static SLPError SAAdvert_for_scopes(SLPHandle, void **); 57 static SLPError slp_unescape(const char *, char **, SLPBoolean, const char); 58 59 /* 60 * Finds scopes according the the user administrative model. 61 */ 62 SLPError SLPFindScopes(SLPHandle hSLP, char **ppcScopes) { 63 SLPError err; 64 char *reply, *unesc_reply; 65 void *stree = NULL; 66 void *collator = NULL; 67 68 if (!hSLP || !ppcScopes) { 69 return (SLP_PARAMETER_BAD); 70 } 71 72 /* first try administratively configured scopes */ 73 if ((err = slp_administrative_scopes(ppcScopes, SLP_FALSE)) 74 != SLP_OK) { 75 return (err); 76 } 77 78 if (*ppcScopes) { 79 /* got scopes */ 80 return (SLP_OK); 81 } 82 83 /* DAs from active and passive discovery */ 84 if ((err = slp_find_das("", &reply)) != SLP_OK && 85 err != SLP_NETWORK_ERROR) 86 return (err); 87 88 /* Unpack the reply */ 89 if (reply) { 90 int numResults = 0; /* placeholder; not actually used */ 91 92 /* tag call as internal */ 93 ((slp_handle_impl_t *)hSLP)->internal_call = SLP_TRUE; 94 95 (void) slp_unpackSrvReply( 96 hSLP, reply, collate_scopes, 97 &stree, &collator, &numResults); 98 /* invoke last call */ 99 (void) slp_unpackSrvReply( 100 hSLP, NULL, collate_scopes, 101 &stree, &collator, &numResults); 102 free(reply); 103 104 /* revert internal call tag */ 105 ((slp_handle_impl_t *)hSLP)->internal_call = SLP_FALSE; 106 } 107 108 /* Finally, if no results yet, try SA discovery */ 109 if (!stree) { 110 (void) SAAdvert_for_scopes(hSLP, &stree); 111 } 112 113 if (!stree) { 114 /* found none, so just return "default" */ 115 if (!(*ppcScopes = strdup("default"))) { 116 slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory"); 117 return (SLP_MEMORY_ALLOC_FAILED); 118 } 119 return (SLP_OK); 120 } 121 122 /* we now have a btree, each leaf of which is a unique scope */ 123 slp_twalk(stree, collect_scopes, 0, (void *) ppcScopes); 124 125 /* unescape scopes list */ 126 if ((err = slp_unescape(*ppcScopes, &unesc_reply, SLP_FALSE, '%')) 127 == SLP_OK) { 128 free(*ppcScopes); 129 *ppcScopes = unesc_reply; 130 } else { 131 free(unesc_reply); 132 } 133 134 return (err); 135 } 136 137 /* 138 * Finds scopes according to the adminstrative scoping model. A 139 * comma-seperated list of scopes is returned in *ppcScopes; the 140 * caller must free *ppcScopes. 141 * If the return_default parameter is true, and no scopes are found, 142 * *ppcScopes will be set to 'default', otherwise, *ppcScopes will 143 * be NULL. This helps simplify internal memory management. 144 */ 145 SLPError slp_administrative_scopes(char **ppcScopes, 146 SLPBoolean return_default) { 147 const char *useScopes; 148 149 *ppcScopes = NULL; 150 151 /* @@@ first try DHCP */ 152 /* next try the useScopes property */ 153 useScopes = SLPGetProperty(SLP_CONFIG_USESCOPES); 154 155 if (useScopes && *useScopes) { 156 if (!(*ppcScopes = strdup(useScopes))) { 157 slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory"); 158 return (SLP_MEMORY_ALLOC_FAILED); 159 } 160 return (SLP_OK); 161 } 162 163 /* found none, so just return "default" */ 164 if (return_default && !(*ppcScopes = strdup("default"))) { 165 slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory"); 166 return (SLP_MEMORY_ALLOC_FAILED); 167 } 168 return (SLP_OK); 169 } 170 171 /* 172 * This function operates on the same btree as the collate_scopes(). 173 * The difference is that this one is called for each 174 * SAAdvert recieved. 175 */ 176 /* ARGSUSED */ 177 static SLPBoolean saadvert_callback(SLPHandle hp, char *scopes, 178 SLPError err, void **stree) { 179 char *s, *tstate; 180 181 if (err != SLP_OK) { 182 return (SLP_TRUE); 183 } 184 185 for ( 186 s = strtok_r((char *)scopes, ",", &tstate); 187 s; 188 s = strtok_r(NULL, ",", &tstate)) { 189 190 char *ascope, **srch; 191 192 if (!(ascope = strdup(s))) { /* no memory! */ 193 slp_err(LOG_CRIT, 0, "collate_scopes", 194 "out of memory"); 195 return (SLP_TRUE); 196 } 197 198 srch = slp_tsearch( 199 (void *) ascope, stree, 200 (int (*)(const void *, const void *)) slp_strcasecmp); 201 if (*srch != ascope) 202 /* scope is already in there, so just free ascope */ 203 free(ascope); 204 } 205 206 return (SLP_TRUE); 207 } 208 209 /* 210 * Generates an SAAdvert solicitation, and returns any scopes found 211 * from all recieved SAAdverts in stree. stree must be a btree 212 * structure. 213 */ 214 static SLPError SAAdvert_for_scopes(SLPHandle hSLP, void **stree) { 215 SLPError err; 216 SLPBoolean sync_state; 217 slp_handle_impl_t *hp = (slp_handle_impl_t *)hSLP; 218 char *predicate; 219 const char *type_hint; 220 221 /* get type hint, if set */ 222 if ((type_hint = SLPGetProperty(SLP_CONFIG_TYPEHINT)) != NULL && 223 *type_hint != 0) { 224 225 size_t hintlen = strlen(type_hint); 226 size_t predlen = strlen("(service-type=)"); 227 228 /* check bounds */ 229 if (hintlen > (SLP_MAX_STRINGLEN - predlen)) { 230 return (SLP_PARAMETER_BAD); 231 } 232 if (!(predicate = malloc(hintlen + predlen + 1))) { 233 slp_err(LOG_CRIT, 0, "SAAdvert_for_scopes", 234 "out of memory"); 235 return (SLP_MEMORY_ALLOC_FAILED); 236 } 237 (void) strcpy(predicate, "(service-type="); 238 (void) strcat(predicate, type_hint); 239 (void) strcat(predicate, ")"); 240 } else { 241 predicate = ""; 242 type_hint = NULL; 243 } 244 245 /* No callback for SLPFindScopes, so force synchronous mode only */ 246 sync_state = hp->async; 247 hp->async = SLP_FALSE; 248 249 if ((err = slp_start_call(hp)) != SLP_OK) 250 return (err); 251 252 err = slp_packSrvRqst("service:service-agent", predicate, hp); 253 254 if (err == SLP_OK) { 255 err = slp_ua_common(hSLP, "", 256 (SLPGenericAppCB *)(uintptr_t)saadvert_callback, 257 stree, 258 (SLPMsgReplyCB *)unpackSAAdvert_scope); 259 } 260 261 if (type_hint) { 262 free(predicate); 263 } 264 265 if (err != SLP_OK) 266 slp_end_call(hp); 267 268 /* restore sync state */ 269 hp->async = sync_state; 270 271 return (err); 272 } 273 274 /* 275 * Unpack an SAAdvert and pass each set of scopes into cb. 276 */ 277 /* ARGSUSED */ 278 static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *hSLP, char *reply, 279 SLPScopeCallback cb, void *cookie, 280 void **collator, int *numResults) { 281 char *surl, *scopes, *attrs; 282 SLPBoolean cont; 283 284 if (!reply) { 285 cb(hSLP, NULL, SLP_LAST_CALL, cookie); 286 return (SLP_FALSE); 287 } 288 289 /* tag call as internal; gets all scopes, regardless of maxResults */ 290 hSLP->internal_call = SLP_TRUE; 291 292 if (slp_unpackSAAdvert(reply, &surl, &scopes, &attrs) != SLP_OK) { 293 return (SLP_TRUE); 294 } 295 296 cont = cb(hSLP, scopes, SLP_OK, cookie); 297 298 /* revert internal_call tag */ 299 hSLP->internal_call = SLP_FALSE; 300 301 free(surl); 302 free(scopes); 303 free(attrs); 304 305 return (cont); 306 } 307 308 /* 309 * Creates a service request for finding DAs or SAs (based on 'filter'), 310 * and sends it to slpd, returning the reply in 'reply'. 311 */ 312 SLPError slp_find_das(const char *filter, char **reply) { 313 SLPError err; 314 char *msg, hostname[MAXHOSTNAMELEN]; 315 316 /* Try the cache first */ 317 if (*reply = slp_find_das_cached(filter)) { 318 return (SLP_OK); 319 } 320 321 /* 322 * create a find scopes message: 323 * this is a SrvRqst for the type directory-agent.sun. 324 */ 325 /* use the local host's name for the scope */ 326 (void) gethostname(hostname, MAXHOSTNAMELEN); 327 328 err = slp_packSrvRqst_single( 329 SLP_SUN_DA_TYPE, hostname, filter, &msg, "en"); 330 331 if (err == SLP_OK) { 332 err = slp_send2slpd(msg, reply); 333 free(msg); 334 } 335 336 /* Add the reply to the cache */ 337 if (err == SLP_OK) { 338 slp_put_das_cached(filter, *reply, slp_get_length(*reply)); 339 } 340 341 return (err); 342 } 343 344 /* 345 * This is called for each URL entry in the DA service reply (sun private). 346 * Contained within the cookie is a btree, to which it adds new 347 * scopes from the URL entry. The scopes are retrieved from the btree 348 * by traversing the tree in SLPFindScopes(). 349 * SLPHandle h is NULL, so don't touch it! 350 */ 351 /*ARGSUSED*/ 352 static SLPBoolean collate_scopes(SLPHandle h, const char *u, 353 unsigned short lifetime, 354 SLPError errCode, void *cookie) { 355 SLPSrvURL *surl; 356 char *s, *tstate, *p, *url; 357 void **collator = cookie; 358 359 if (errCode != SLP_OK) 360 return (SLP_TRUE); 361 362 /* dup url so as not to corrupt da cache */ 363 if (!(url = strdup(u))) { 364 slp_err(LOG_CRIT, 0, "collate_scopes", "out of memory"); 365 return (SLP_FALSE); 366 } 367 368 /* parse url into a SLPSrvURL struct */ 369 if (SLPParseSrvURL(url, &surl) != SLP_OK) 370 return (SLP_TRUE); /* bad URL; skip it */ 371 372 /* collate the scopes using the btree stree->scopes: */ 373 /* skip the 'scopes=' part */ 374 if (!(p = strchr(surl->s_pcSrvPart, '='))) { 375 free(surl); 376 return (SLP_TRUE); /* bad URL; skip it */ 377 } 378 p++; 379 380 for ( 381 s = strtok_r(p, ",", &tstate); 382 s; 383 s = strtok_r(NULL, ",", &tstate)) { 384 385 char *ascope, **srch; 386 387 if (!(ascope = strdup(s))) { /* no memory! */ 388 slp_err(LOG_CRIT, 0, "collate_scopes", 389 "out of memory"); 390 free(surl); 391 return (SLP_TRUE); 392 } 393 394 srch = slp_tsearch( 395 (void *) ascope, collator, 396 (int (*)(const void *, const void *)) slp_strcasecmp); 397 if (*srch != ascope) 398 /* scope is already in there, so just free ascope */ 399 free(ascope); 400 } 401 402 free(url); 403 free(surl); 404 405 return (SLP_TRUE); 406 } 407 408 /* 409 * Each time we visit a node for the last time, copy that scope into 410 * the scope collection and free the scope string and the node. 411 */ 412 /*ARGSUSED*/ 413 static void collect_scopes(void *node, VISIT order, int level, void *cookie) { 414 char **scopes = (char **)cookie; 415 416 if (order == endorder || order == leaf) { 417 char *s = *(char **)node; 418 slp_add2list(s, scopes, SLP_FALSE); 419 free(s); 420 free(node); 421 } 422 } 423 424 void SLPFree(void *pvMem) { 425 if (pvMem) 426 free(pvMem); 427 } 428 429 /* 430 * Escape / Unescape 431 */ 432 433 #define isBadTagChar(c) ((c) == '*' || (c) == '_' || \ 434 (c) == '\n' || (c) == '\t' || (c) == '\r') 435 436 #define isReserved(c) ((c) <= 31 || (c) == '(' || (c) == ')' || \ 437 (c) == ',' || (c) == '\\' || (c) == '!' || \ 438 (c) == '<' || (c) == '=' || (c) == '>' || \ 439 (c) == '~') 440 441 SLPError SLPEscape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) { 442 char *buf, *pin, *pout; 443 444 if (!pcInbuf || !ppcOutBuf) 445 return (SLP_PARAMETER_BAD); 446 447 if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) { 448 slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory"); 449 return (SLP_MEMORY_ALLOC_FAILED); 450 } 451 *ppcOutBuf = buf; 452 453 for (pin = (char *)pcInbuf, pout = buf; *pin; ) { 454 int len; 455 456 /* If char is start of multibyte char, just copy it in */ 457 if ((len = mblen(pin, MB_CUR_MAX)) > 1) { 458 int i; 459 for (i = 0; i < len && *pin; i++) 460 *pout++ = *pin++; 461 continue; 462 } 463 464 /* check for bad tag */ 465 if (isTag && isBadTagChar(*pin)) 466 return (SLP_PARSE_ERROR); 467 468 if (isReserved(*pin)) { 469 if (isTag) 470 return (SLP_PARSE_ERROR); 471 (void) sprintf(pout, "\\%.2x", *pin); 472 pout += 3; 473 pin++; 474 } else { 475 *pout++ = *pin++; 476 } 477 } 478 *pout = 0; 479 480 return (SLP_OK); 481 } 482 483 SLPError SLPUnescape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) { 484 if (!pcInbuf || !ppcOutBuf) 485 return (SLP_PARAMETER_BAD); 486 487 return (slp_unescape(pcInbuf, ppcOutBuf, isTag, '\\')); 488 } 489 490 491 /* 492 * The actual unescaping routine; allows for different escape chars. 493 */ 494 static SLPError slp_unescape(const char *pcInbuf, char **ppcOutBuf, 495 SLPBoolean isTag, const char esc_char) { 496 char *buf, *pin, *pout, conv[3]; 497 498 if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) { 499 slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory"); 500 return (SLP_MEMORY_ALLOC_FAILED); 501 } 502 *ppcOutBuf = buf; 503 504 conv[2] = 0; 505 for (pin = (char *)pcInbuf, pout = buf; *pin; ) { 506 int len; 507 508 /* If char is start of multibyte char, just copy it in */ 509 if ((len = mblen(pin, MB_CUR_MAX)) > 1) { 510 int i; 511 for (i = 0; i < len && *pin; i++) 512 *pout++ = *pin++; 513 continue; 514 } 515 516 if (*pin == esc_char) { 517 if (!pin[1] || !pin[2]) 518 return (SLP_PARSE_ERROR); 519 pin++; 520 conv[0] = *pin++; 521 conv[1] = *pin++; 522 *pout++ = (char)strtol(conv, NULL, 16); 523 if (isTag && isBadTagChar(*pout)) 524 return (SLP_PARSE_ERROR); 525 } else { 526 *pout++ = *pin++; 527 } 528 } 529 *pout = 0; 530 531 return (SLP_OK); 532 } 533 534 /* 535 * Properties 536 * 537 * All properties are stored in a global tree (prop_table). This 538 * tree is created and accessed by slp_tsearch and slp_tfind. 539 */ 540 struct prop_entry { 541 const char *key, *val; 542 }; 543 typedef struct prop_entry slp_prop_entry_t; 544 545 /* Global properties table */ 546 static void *slp_props = NULL; 547 static mutex_t prop_table_lock = DEFAULTMUTEX; 548 549 static void setDefaults(); 550 551 static int compare_props(const void *a, const void *b) { 552 return (strcmp( 553 ((slp_prop_entry_t *)a)->key, 554 ((slp_prop_entry_t *)b)->key)); 555 } 556 557 void SLPSetProperty(const char *pcName, const char *pcValue) { 558 slp_prop_entry_t *pe, **pe2; 559 560 if (!slp_props) setDefaults(); 561 562 if (!pcName || !pcValue) { 563 return; 564 } 565 566 if (!(pe = malloc(sizeof (*pe)))) { 567 slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory"); 568 return; 569 } 570 571 /* place the strings under library ownership */ 572 if (!(pe->key = strdup(pcName))) { 573 free(pe); 574 slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory"); 575 return; 576 } 577 578 if (!(pe->val = strdup(pcValue))) { 579 free((void *) pe->key); 580 free(pe); 581 slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory"); 582 return; 583 } 584 585 /* is pcName already set? */ 586 (void) mutex_lock(&prop_table_lock); 587 pe2 = slp_tsearch((void *) pe, &slp_props, compare_props); 588 if (pe != *pe2) { 589 /* this prop is already set; overwrite the old value */ 590 free((void *) (*pe2)->val); 591 (*pe2)->val = pe->val; 592 free((void *) pe->key); 593 free(pe); 594 } 595 (void) mutex_unlock(&prop_table_lock); 596 } 597 598 const char *SLPGetProperty(const char *pcName) { 599 slp_prop_entry_t pe[1], **ans; 600 601 if (!slp_props) setDefaults(); 602 603 if (!pcName) { 604 return (NULL); 605 } 606 607 pe->key = pcName; 608 609 (void) mutex_lock(&prop_table_lock); 610 ans = slp_tfind(pe, &slp_props, compare_props); 611 (void) mutex_unlock(&prop_table_lock); 612 if (ans) 613 return ((*ans)->val); 614 return (NULL); 615 } 616 617 static void setDefaults() { 618 slp_prop_entry_t *pe; 619 static mutex_t lock = DEFAULTMUTEX; 620 621 (void) mutex_lock(&lock); 622 if (slp_props) { 623 (void) mutex_unlock(&lock); 624 return; 625 } 626 627 pe = malloc(sizeof (*pe)); 628 pe->key = strdup(SLP_CONFIG_ISBROADCASTONLY); 629 pe->val = strdup("false"); 630 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 631 632 pe = malloc(sizeof (*pe)); 633 pe->key = strdup(SLP_CONFIG_MULTICASTTTL); 634 pe->val = strdup("255"); 635 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 636 637 pe = malloc(sizeof (*pe)); 638 pe->key = strdup(SLP_CONFIG_MULTICASTMAXWAIT); 639 pe->val = strdup("15000"); 640 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 641 642 pe = malloc(sizeof (*pe)); 643 pe->key = strdup(SLP_CONFIG_DATAGRAMTIMEOUTS); 644 pe->val = strdup("2000,2000,2000"); 645 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 646 647 pe = malloc(sizeof (*pe)); 648 pe->key = strdup(SLP_CONFIG_MULTICASTTIMEOUTS); 649 pe->val = strdup("1000,3000,3000,3000,3000"); 650 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 651 652 pe = malloc(sizeof (*pe)); 653 pe->key = SLP_CONFIG_MTU; pe->val = "1400"; 654 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 655 656 pe = malloc(sizeof (*pe)); 657 pe->key = strdup(SLP_CONFIG_MAXRESULTS); 658 pe->val = strdup("-1"); 659 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 660 661 pe = malloc(sizeof (*pe)); 662 pe->key = strdup(SLP_CONFIG_SECURITY_ON); 663 pe->val = strdup("false"); 664 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 665 666 pe = malloc(sizeof (*pe)); 667 pe->key = strdup(SLP_CONFIG_BYPASS_AUTH); 668 pe->val = strdup("false"); 669 (void) slp_tsearch((void *) pe, &slp_props, compare_props); 670 671 slp_readConfig(); 672 673 (void) mutex_unlock(&lock); 674 } 675 676 static const char *error_strings[] = { 677 "OK", /* 0 */ 678 "Language not supported", /* -1 */ 679 "Parse error", /* -2 */ 680 "Invalid registration", /* -3 */ 681 "Scope not supported", /* -4 */ 682 "Invalid error number", /* -5 */ 683 "Authentication absent", /* -6 */ 684 "Authentication failed", /* -7 */ 685 "Invalid error number", /* -8 */ 686 "Invalid error number", /* -9 */ 687 "Invalid error number", /* -10 */ 688 "Invalid error number", /* -11 */ 689 "Invalid error number", /* -12 */ 690 "Invalid update", /* -13 */ 691 "Invalid error number", /* -14 */ 692 "Invalid error number", /* -15 */ 693 "Invalid error number", /* -16 */ 694 "Not implemented", /* -17 */ 695 "Buffer overflow", /* -18 */ 696 "Network timed out", /* -19 */ 697 "Network init failed", /* -20 */ 698 "Memory alloc failed", /* -21 */ 699 "Parameter bad", /* -22 */ 700 "Network error", /* -23 */ 701 "Internal system error", /* -24 */ 702 "Handle in use", /* -25 */ 703 "Type error" /* -26 */ 704 }; 705 706 #define SLP_MAX_ERR_CNT 26 707 708 const char *slp_strerror(SLPError err) { 709 int abserr; 710 const char *str; 711 712 if (err == SLP_LAST_CALL) { 713 str = "Last call"; 714 } else if (err == SLP_SECURITY_UNAVAILABLE) { 715 str = "Security Implementation Unavailable"; 716 } else { 717 abserr = abs(err); 718 if (abserr > SLP_MAX_ERR_CNT) { 719 str = "Invalid error number"; 720 } else { 721 str = error_strings[abserr]; 722 } 723 } 724 725 return (dgettext("libslp", str)); 726 } 727