1 /* $OpenBSD: to.c,v 1.50 2023年05月31日 16:51:46 op Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 5 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> 6 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21#include <arpa/inet.h> 22#include <ctype.h> 23#include <errno.h> 24#include <stdlib.h> 25#include <string.h> 26#include <time.h> 27#if IO_TLS 28#include <tls.h> 29#endif 30 31#include "smtpd.h" 32#include "log.h" 33 34 static int alias_is_filter(struct expandnode *, const char *, size_t); 35 static int alias_is_username(struct expandnode *, const char *, size_t); 36 static int alias_is_address(struct expandnode *, const char *, size_t); 37 static int alias_is_filename(struct expandnode *, const char *, size_t); 38 static int alias_is_include(struct expandnode *, const char *, size_t); 39 static int alias_is_error(struct expandnode *, const char *, size_t); 40 41 const char * 42 sockaddr_to_text(const struct sockaddr *sa) 43{ 44 static char buf[NI_MAXHOST]; 45 46 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, 47 NI_NUMERICHOST)) 48 return ("(unknown)"); 49 else 50 return (buf); 51} 52 53 int 54 text_to_mailaddr(struct mailaddr *maddr, const char *email) 55{ 56 char *username; 57 char *hostname; 58 char buffer[LINE_MAX]; 59 60 if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer) 61 return 0; 62 63 memset(maddr, 0, sizeof *maddr); 64 65 username = buffer; 66 hostname = strrchr(username, '@'); 67 68 if (hostname == NULL) { 69 if (strlcpy(maddr->user, username, sizeof maddr->user) 70 >= sizeof maddr->user) 71 return 0; 72 } 73 else if (username == hostname) { 74 *hostname++ = '0円'; 75 if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) 76 >= sizeof maddr->domain) 77 return 0; 78 } 79 else { 80 *hostname++ = '0円'; 81 if (strlcpy(maddr->user, username, sizeof maddr->user) 82 >= sizeof maddr->user) 83 return 0; 84 if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) 85 >= sizeof maddr->domain) 86 return 0; 87 } 88 89 return 1; 90} 91 92 const char * 93 mailaddr_to_text(const struct mailaddr *maddr) 94{ 95 static char buffer[LINE_MAX]; 96 97 (void)strlcpy(buffer, maddr->user, sizeof buffer); 98 (void)strlcat(buffer, "@", sizeof buffer); 99 if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer) 100 return NULL; 101 102 return buffer; 103} 104 105 106 const char * 107 sa_to_text(const struct sockaddr *sa) 108{ 109 static char buf[NI_MAXHOST + 5]; 110 char *p; 111 112 buf[0] = '0円'; 113 p = buf; 114 115 if (sa->sa_family == AF_LOCAL) 116 (void)strlcpy(buf, "local", sizeof buf); 117 else if (sa->sa_family == AF_INET) { 118 in_addr_t addr; 119 120 addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr; 121 addr = ntohl(addr); 122 (void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d", 123 (addr >> 24) & 0xff, (addr >> 16) & 0xff, 124 (addr >> 8) & 0xff, addr & 0xff); 125 } 126 else if (sa->sa_family == AF_INET6) { 127 (void)bsnprintf(p, NI_MAXHOST, "[%s]", sockaddr_to_text(sa)); 128 } 129 130 return (buf); 131} 132 133 const char * 134 ss_to_text(const struct sockaddr_storage *ss) 135{ 136 return (sa_to_text((const struct sockaddr*)ss)); 137} 138 139 const char * 140 time_to_text(time_t when) 141{ 142 struct tm *lt; 143 static char buf[40]; 144 const char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 145 const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", 146 "Jul","Aug","Sep","Oct","Nov","Dec"}; 147 const char *tz; 148 long offset; 149 150 lt = localtime(&when); 151 if (lt == NULL || when == 0) 152 fatalx("time_to_text: localtime"); 153 154 offset = lt->tm_gmtoff; 155 tz = lt->tm_zone; 156 157 /* We do not use strftime because it is subject to locale substitution*/ 158 if (!bsnprintf(buf, sizeof(buf), 159 "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", 160 day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], 161 lt->tm_year + 1900, 162 lt->tm_hour, lt->tm_min, lt->tm_sec, 163 offset >= 0 ? '+' : '-', 164 abs((int)offset / 3600), 165 abs((int)offset % 3600) / 60, 166 tz)) 167 fatalx("time_to_text: bsnprintf"); 168 169 return buf; 170} 171 172 const char * 173 duration_to_text(time_t t) 174{ 175 static char dst[64]; 176 char buf[64]; 177 int h, m, s; 178 long long d; 179 180 if (t == 0) { 181 (void)strlcpy(dst, "0s", sizeof dst); 182 return (dst); 183 } 184 185 dst[0] = '0円'; 186 if (t < 0) { 187 (void)strlcpy(dst, "-", sizeof dst); 188 t = -t; 189 } 190 191 s = t % 60; 192 t /= 60; 193 m = t % 60; 194 t /= 60; 195 h = t % 24; 196 d = t / 24; 197 198 if (d) { 199 (void)snprintf(buf, sizeof buf, "%lldd", d); 200 (void)strlcat(dst, buf, sizeof dst); 201 } 202 if (h) { 203 (void)snprintf(buf, sizeof buf, "%dh", h); 204 (void)strlcat(dst, buf, sizeof dst); 205 } 206 if (m) { 207 (void)snprintf(buf, sizeof buf, "%dm", m); 208 (void)strlcat(dst, buf, sizeof dst); 209 } 210 if (s) { 211 (void)snprintf(buf, sizeof buf, "%ds", s); 212 (void)strlcat(dst, buf, sizeof dst); 213 } 214 215 return (dst); 216} 217 218 int 219 text_to_netaddr(struct netaddr *netaddr, const char *s) 220{ 221 struct sockaddr_storage ss; 222 struct sockaddr_in ssin; 223 struct sockaddr_in6 ssin6; 224 int bits; 225 char buf[NI_MAXHOST]; 226 size_t len; 227 228 memset(&ssin, 0, sizeof(struct sockaddr_in)); 229 memset(&ssin6, 0, sizeof(struct sockaddr_in6)); 230 231 if (strncasecmp("IPv6:", s, 5) == 0) 232 s += 5; 233 234 bits = inet_net_pton(AF_INET, s, &ssin.sin_addr, 235 sizeof(struct in_addr)); 236 if (bits != -1) { 237 ssin.sin_family = AF_INET; 238 memcpy(&ss, &ssin, sizeof(ssin)); 239 ss.ss_len = sizeof(struct sockaddr_in); 240 } else { 241 if (s[0] != '[') { 242 if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) 243 return 0; 244 } 245 else { 246 s++; 247 if (strncasecmp("IPv6:", s, 5) == 0) 248 s += 5; 249 if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) 250 return 0; 251 if (buf[len-1] != ']') 252 return 0; 253 buf[len-1] = 0; 254 } 255 bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr, 256 sizeof(struct in6_addr)); 257 if (bits == -1) 258 return 0; 259 ssin6.sin6_family = AF_INET6; 260 memcpy(&ss, &ssin6, sizeof(ssin6)); 261 ss.ss_len = sizeof(struct sockaddr_in6); 262 } 263 264 netaddr->ss = ss; 265 netaddr->bits = bits; 266 return 1; 267} 268 269 int 270 text_to_relayhost(struct relayhost *relay, const char *s) 271{ 272 static const struct schema { 273 const char *name; 274 int tls; 275 uint16_t flags; 276 uint16_t port; 277 } schemas [] = { 278 /* 279 * new schemas should be *appended* otherwise the default 280 * schema index needs to be updated later in this function. 281 */ 282 { "smtp://", RELAY_TLS_OPPORTUNISTIC, 0, 25 }, 283 { "smtp+tls://", RELAY_TLS_STARTTLS, 0, 25 }, 284 { "smtp+notls://", RELAY_TLS_NO, 0, 25 }, 285 /* need to specify an explicit port for LMTP */ 286 { "lmtp://", RELAY_TLS_NO, RELAY_LMTP, 0 }, 287 { "smtps://", RELAY_TLS_SMTPS, 0, 465 } 288 }; 289 const char *errstr = NULL; 290 char *p, *q; 291 char buffer[1024]; 292 char *beg, *end; 293 size_t i; 294 size_t len; 295 296 memset(buffer, 0, sizeof buffer); 297 if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) 298 return 0; 299 300 for (i = 0; i < nitems(schemas); ++i) 301 if (strncasecmp(schemas[i].name, s, 302 strlen(schemas[i].name)) == 0) 303 break; 304 305 if (i == nitems(schemas)) { 306 /* there is a schema, but it's not recognized */ 307 if (strstr(buffer, "://")) 308 return 0; 309 310 /* no schema, default to smtp:// */ 311 i = 0; 312 p = buffer; 313 } 314 else 315 p = buffer + strlen(schemas[i].name); 316 317 relay->tls = schemas[i].tls; 318 relay->flags = schemas[i].flags; 319 relay->port = schemas[i].port; 320 321 /* first, we extract the label if any */ 322 if ((q = strchr(p, '@')) != NULL) { 323 *q = 0; 324 if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel)) 325 >= sizeof (relay->authlabel)) 326 return 0; 327 p = q + 1; 328 } 329 330 /* then, we extract the mail exchanger */ 331 beg = end = p; 332 if (*beg == '[') { 333 if ((end = strchr(beg, ']')) == NULL) 334 return 0; 335 /* skip ']', it has to be included in the relay hostname */ 336 ++end; 337 len = end - beg; 338 } 339 else { 340 for (end = beg; *end; ++end) 341 if (!isalnum((unsigned char)*end) && 342 *end != '_' && *end != '.' && *end != '-') 343 break; 344 len = end - beg; 345 } 346 if (len >= sizeof relay->hostname) 347 return 0; 348 for (i = 0; i < len; ++i) 349 relay->hostname[i] = beg[i]; 350 relay->hostname[i] = 0; 351 352 /* finally, we extract the port */ 353 p = beg + len; 354 if (*p == ':') { 355 relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr); 356 if (errstr) 357 return 0; 358 } 359 360 if (!valid_domainpart(relay->hostname)) 361 return 0; 362 if ((relay->flags & RELAY_LMTP) && (relay->port == 0)) 363 return 0; 364 if (relay->authlabel[0]) { 365 /* disallow auth on non-tls scheme. */ 366 if (relay->tls != RELAY_TLS_STARTTLS && 367 relay->tls != RELAY_TLS_SMTPS) 368 return 0; 369 relay->flags |= RELAY_AUTH; 370 } 371 372 return 1; 373} 374 375 uint64_t 376 text_to_evpid(const char *s) 377{ 378 uint64_t ulval; 379 char *ep; 380 381 errno = 0; 382 ulval = strtoull(s, &ep, 16); 383 if (s[0] == '0円' || *ep != '0円') 384 return 0; 385 if (errno == ERANGE && ulval == ULLONG_MAX) 386 return 0; 387 if (ulval == 0) 388 return 0; 389 return (ulval); 390} 391 392 uint32_t 393 text_to_msgid(const char *s) 394{ 395 uint64_t ulval; 396 char *ep; 397 398 errno = 0; 399 ulval = strtoull(s, &ep, 16); 400 if (s[0] == '0円' || *ep != '0円') 401 return 0; 402 if (errno == ERANGE && ulval == ULLONG_MAX) 403 return 0; 404 if (ulval == 0) 405 return 0; 406 if (ulval > 0xffffffff) 407 return 0; 408 return (ulval & 0xffffffff); 409} 410 411 const char * 412 rule_to_text(struct rule *r) 413{ 414 static char buf[4096]; 415 416 memset(buf, 0, sizeof buf); 417 (void)strlcpy(buf, "match", sizeof buf); 418 if (r->flag_tag) { 419 if (r->flag_tag < 0) 420 (void)strlcat(buf, " !", sizeof buf); 421 (void)strlcat(buf, " tag ", sizeof buf); 422 (void)strlcat(buf, r->table_tag, sizeof buf); 423 } 424 425 if (r->flag_from) { 426 if (r->flag_from < 0) 427 (void)strlcat(buf, " !", sizeof buf); 428 if (r->flag_from_socket) 429 (void)strlcat(buf, " from socket", sizeof buf); 430 else if (r->flag_from_rdns) { 431 (void)strlcat(buf, " from rdns", sizeof buf); 432 if (r->table_from) { 433 (void)strlcat(buf, " ", sizeof buf); 434 (void)strlcat(buf, r->table_from, sizeof buf); 435 } 436 } 437 else if (strcmp(r->table_from, "<anyhost>") == 0) 438 (void)strlcat(buf, " from any", sizeof buf); 439 else if (strcmp(r->table_from, "<localhost>") == 0) 440 (void)strlcat(buf, " from local", sizeof buf); 441 else { 442 (void)strlcat(buf, " from src ", sizeof buf); 443 (void)strlcat(buf, r->table_from, sizeof buf); 444 } 445 } 446 447 if (r->flag_for) { 448 if (r->flag_for < 0) 449 (void)strlcat(buf, " !", sizeof buf); 450 if (strcmp(r->table_for, "<anydestination>") == 0) 451 (void)strlcat(buf, " for any", sizeof buf); 452 else if (strcmp(r->table_for, "<localnames>") == 0) 453 (void)strlcat(buf, " for local", sizeof buf); 454 else { 455 (void)strlcat(buf, " for domain ", sizeof buf); 456 (void)strlcat(buf, r->table_for, sizeof buf); 457 } 458 } 459 460 if (r->flag_smtp_helo) { 461 if (r->flag_smtp_helo < 0) 462 (void)strlcat(buf, " !", sizeof buf); 463 (void)strlcat(buf, " helo ", sizeof buf); 464 (void)strlcat(buf, r->table_smtp_helo, sizeof buf); 465 } 466 467 if (r->flag_smtp_auth) { 468 if (r->flag_smtp_auth < 0) 469 (void)strlcat(buf, " !", sizeof buf); 470 (void)strlcat(buf, " auth", sizeof buf); 471 if (r->table_smtp_auth) { 472 (void)strlcat(buf, " ", sizeof buf); 473 (void)strlcat(buf, r->table_smtp_auth, sizeof buf); 474 } 475 } 476 477 if (r->flag_smtp_starttls) { 478 if (r->flag_smtp_starttls < 0) 479 (void)strlcat(buf, " !", sizeof buf); 480 (void)strlcat(buf, " tls", sizeof buf); 481 } 482 483 if (r->flag_smtp_mail_from) { 484 if (r->flag_smtp_mail_from < 0) 485 (void)strlcat(buf, " !", sizeof buf); 486 (void)strlcat(buf, " mail-from ", sizeof buf); 487 (void)strlcat(buf, r->table_smtp_mail_from, sizeof buf); 488 } 489 490 if (r->flag_smtp_rcpt_to) { 491 if (r->flag_smtp_rcpt_to < 0) 492 (void)strlcat(buf, " !", sizeof buf); 493 (void)strlcat(buf, " rcpt-to ", sizeof buf); 494 (void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf); 495 } 496 (void)strlcat(buf, " action ", sizeof buf); 497 if (r->reject) 498 (void)strlcat(buf, "reject", sizeof buf); 499 else 500 (void)strlcat(buf, r->dispatcher, sizeof buf); 501 return buf; 502} 503 504 505 int 506 text_to_userinfo(struct userinfo *userinfo, const char *s) 507{ 508 char buf[PATH_MAX]; 509 char *p; 510 const char *errstr; 511 512 memset(buf, 0, sizeof buf); 513 p = buf; 514 while (*s && *s != ':') 515 *p++ = *s++; 516 if (*s++ != ':') 517 goto error; 518 519 if (strlcpy(userinfo->username, buf, 520 sizeof userinfo->username) >= sizeof userinfo->username) 521 goto error; 522 523 memset(buf, 0, sizeof buf); 524 p = buf; 525 while (*s && *s != ':') 526 *p++ = *s++; 527 if (*s++ != ':') 528 goto error; 529 userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr); 530 if (errstr) 531 goto error; 532 533 memset(buf, 0, sizeof buf); 534 p = buf; 535 while (*s && *s != ':') 536 *p++ = *s++; 537 if (*s++ != ':') 538 goto error; 539 userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr); 540 if (errstr) 541 goto error; 542 543 if (strlcpy(userinfo->directory, s, 544 sizeof userinfo->directory) >= sizeof userinfo->directory) 545 goto error; 546 547 return 1; 548 549 error: 550 return 0; 551} 552 553 int 554 text_to_credentials(struct credentials *creds, const char *s) 555{ 556 char *p; 557 char buffer[LINE_MAX]; 558 size_t offset; 559 560 p = strchr(s, ':'); 561 if (p == NULL) { 562 creds->username[0] = '0円'; 563 if (strlcpy(creds->password, s, sizeof creds->password) 564 >= sizeof creds->password) 565 return 0; 566 return 1; 567 } 568 569 offset = p - s; 570 571 memset(buffer, 0, sizeof buffer); 572 if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) 573 return 0; 574 p = buffer + offset; 575 *p = '0円'; 576 577 if (strlcpy(creds->username, buffer, sizeof creds->username) 578 >= sizeof creds->username) 579 return 0; 580 if (strlcpy(creds->password, p+1, sizeof creds->password) 581 >= sizeof creds->password) 582 return 0; 583 584 return 1; 585} 586 587 int 588 text_to_expandnode(struct expandnode *expandnode, const char *s) 589{ 590 size_t l; 591 592 l = strlen(s); 593 if (alias_is_error(expandnode, s, l) || 594 alias_is_include(expandnode, s, l) || 595 alias_is_filter(expandnode, s, l) || 596 alias_is_filename(expandnode, s, l) || 597 alias_is_address(expandnode, s, l) || 598 alias_is_username(expandnode, s, l)) 599 return (1); 600 601 return (0); 602} 603 604 const char * 605 expandnode_to_text(struct expandnode *expandnode) 606{ 607 switch (expandnode->type) { 608 case EXPAND_FILTER: 609 case EXPAND_FILENAME: 610 case EXPAND_INCLUDE: 611 case EXPAND_ERROR: 612 case EXPAND_USERNAME: 613 return expandnode->u.user; 614 case EXPAND_ADDRESS: 615 return mailaddr_to_text(&expandnode->u.mailaddr); 616 case EXPAND_INVALID: 617 break; 618 } 619 620 return NULL; 621} 622 623 /******/ 624 static int 625 alias_is_filter(struct expandnode *alias, const char *line, size_t len) 626{ 627 int v = 0; 628 629 if (*line == '"') 630 v = 1; 631 if (*(line+v) == '|') { 632 if (strlcpy(alias->u.buffer, line + v + 1, 633 sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) 634 return 0; 635 if (v) { 636 v = strlen(alias->u.buffer); 637 if (v == 0) 638 return (0); 639 if (alias->u.buffer[v-1] != '"') 640 return (0); 641 alias->u.buffer[v-1] = '0円'; 642 } 643 alias->type = EXPAND_FILTER; 644 return (1); 645 } 646 return (0); 647} 648 649 static int 650 alias_is_username(struct expandnode *alias, const char *line, size_t len) 651{ 652 memset(alias, 0, sizeof *alias); 653 654 if (strlcpy(alias->u.user, line, 655 sizeof(alias->u.user)) >= sizeof(alias->u.user)) 656 return 0; 657 658 while (*line) { 659 if (!isalnum((unsigned char)*line) && 660 *line != '_' && *line != '.' && *line != '-' && *line != '+') 661 return 0; 662 ++line; 663 } 664 665 alias->type = EXPAND_USERNAME; 666 return 1; 667} 668 669 static int 670 alias_is_address(struct expandnode *alias, const char *line, size_t len) 671{ 672 char *domain; 673 674 memset(alias, 0, sizeof *alias); 675 676 if (len < 3) /* x@y */ 677 return 0; 678 679 domain = strchr(line, '@'); 680 if (domain == NULL) 681 return 0; 682 683 /* @ cannot start or end an address */ 684 if (domain == line || domain == line + len - 1) 685 return 0; 686 687 /* scan pre @ for disallowed chars */ 688 *domain++ = '0円'; 689 (void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user)); 690 (void)strlcpy(alias->u.mailaddr.domain, domain, 691 sizeof(alias->u.mailaddr.domain)); 692 693 while (*line) { 694 char allowedset[] = "!#$%*/?|^{}`~&'+-=_."; 695 if (!isalnum((unsigned char)*line) && 696 strchr(allowedset, *line) == NULL) 697 return 0; 698 ++line; 699 } 700 701 while (*domain) { 702 char allowedset[] = "-."; 703 if (!isalnum((unsigned char)*domain) && 704 strchr(allowedset, *domain) == NULL) 705 return 0; 706 ++domain; 707 } 708 709 alias->type = EXPAND_ADDRESS; 710 return 1; 711} 712 713 static int 714 alias_is_filename(struct expandnode *alias, const char *line, size_t len) 715{ 716 memset(alias, 0, sizeof *alias); 717 718 if (*line != '/') 719 return 0; 720 721 if (strlcpy(alias->u.buffer, line, 722 sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) 723 return 0; 724 alias->type = EXPAND_FILENAME; 725 return 1; 726} 727 728 static int 729 alias_is_include(struct expandnode *alias, const char *line, size_t len) 730{ 731 size_t skip; 732 733 memset(alias, 0, sizeof *alias); 734 735 if (strncasecmp(":include:", line, 9) == 0) 736 skip = 9; 737 else if (strncasecmp("include:", line, 8) == 0) 738 skip = 8; 739 else 740 return 0; 741 742 if (!alias_is_filename(alias, line + skip, len - skip)) 743 return 0; 744 745 alias->type = EXPAND_INCLUDE; 746 return 1; 747} 748 749 static int 750 alias_is_error(struct expandnode *alias, const char *line, size_t len) 751{ 752 size_t skip; 753 754 memset(alias, 0, sizeof *alias); 755 756 if (strncasecmp(":error:", line, 7) == 0) 757 skip = 7; 758 else if (strncasecmp("error:", line, 6) == 0) 759 skip = 6; 760 else 761 return 0; 762 763 if (strlcpy(alias->u.buffer, line + skip, 764 sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) 765 return 0; 766 767 if (strlen(alias->u.buffer) < 5) 768 return 0; 769 770 /* [45][0-9]{2} [a-zA-Z0-9].* */ 771 if (alias->u.buffer[3] != ' ' || 772 !isalnum((unsigned char)alias->u.buffer[4]) || 773 (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') || 774 !isdigit((unsigned char)alias->u.buffer[1]) || 775 !isdigit((unsigned char)alias->u.buffer[2])) 776 return 0; 777 778 alias->type = EXPAND_ERROR; 779 return 1; 780} 781 782#if IO_TLS 783 const char * 784 tls_to_text(struct tls *tls) 785{ 786 static char buf[256]; 787 788 (void)snprintf(buf, sizeof buf, "%s:%s:%d", 789 tls_conn_version(tls), 790 tls_conn_cipher(tls), 791 tls_conn_cipher_strength(tls)); 792 793 return (buf); 794} 795#endif 796