summaryrefslogtreecommitdiff
path: root/sshconnect.c
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2010-12-01 12:21:51 +1100
committerDamien Miller <djm@mindrot.org>2010-12-01 12:21:51 +1100
commitd925dcd8a5d1a3070061006788352bed93260582 (patch)
tree12f78195086ff506d0f4e4c39098d675cdae0ee9 /sshconnect.c
parent03c0e533de56a1fc55ec1885d35c3197fdefbf94 (diff)
- djm@cvs.openbsd.org 2010/11/29 23:45:51
[auth.c hostfile.c hostfile.h ssh.c ssh_config.5 sshconnect.c] [sshconnect.h sshconnect2.c] automatically order the hostkeys requested by the client based on which hostkeys are already recorded in known_hosts. This avoids hostkey warnings when connecting to servers with new ECDSA keys that are preferred by default; with markus@
Diffstat (limited to 'sshconnect.c')
-rw-r--r--sshconnect.c291
1 files changed, 153 insertions, 138 deletions
diff --git a/sshconnect.c b/sshconnect.c
index 78068c602..064bb74b3 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: sshconnect.c,v 1.228 2010/10/06 21:10:21 djm Exp $ */ 1/* $OpenBSD: sshconnect.c,v 1.229 2010/11/29 23:45:51 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -75,7 +75,7 @@ extern char *__progname;
75extern uid_t original_real_uid; 75extern uid_t original_real_uid;
76extern uid_t original_effective_uid; 76extern uid_t original_effective_uid;
77 77
78static int show_other_keys(const char *, Key *); 78static int show_other_keys(struct hostkeys *, Key *);
79static void warn_changed_key(Key *); 79static void warn_changed_key(Key *);
80 80
81/* 81/*
@@ -607,6 +607,79 @@ check_host_cert(const char *host, const Key *host_key)
607 return 1; 607 return 1;
608} 608}
609 609
610static int
611sockaddr_is_local(struct sockaddr *hostaddr)
612{
613 switch (hostaddr->sa_family) {
614 case AF_INET:
615 return (ntohl(((struct sockaddr_in *)hostaddr)->
616 sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
617 case AF_INET6:
618 return IN6_IS_ADDR_LOOPBACK(
619 &(((struct sockaddr_in6 *)hostaddr)->sin6_addr));
620 default:
621 return 0;
622 }
623}
624
625/*
626 * Prepare the hostname and ip address strings that are used to lookup
627 * host keys in known_hosts files. These may have a port number appended.
628 */
629void
630get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr,
631 u_short port, char **hostfile_hostname, char **hostfile_ipaddr)
632{
633 char ntop[NI_MAXHOST];
634 socklen_t addrlen;
635
636 switch (hostaddr == NULL ? -1 : hostaddr->sa_family) {
637 case -1:
638 addrlen = 0;
639 break;
640 case AF_INET:
641 addrlen = sizeof(struct sockaddr_in);
642 break;
643 case AF_INET6:
644 addrlen = sizeof(struct sockaddr_in6);
645 break;
646 default:
647 addrlen = sizeof(struct sockaddr);
648 break;
649 }
650
651 /*
652 * We don't have the remote ip-address for connections
653 * using a proxy command
654 */
655 if (hostfile_ipaddr != NULL) {
656 if (options.proxy_command == NULL) {
657 if (getnameinfo(hostaddr, addrlen,
658 ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0)
659 fatal("check_host_key: getnameinfo failed");
660 *hostfile_ipaddr = put_host_port(ntop, port);
661 } else {
662 *hostfile_ipaddr = xstrdup("<no hostip for proxy "
663 "command>");
664 }
665 }
666
667 /*
668 * Allow the user to record the key under a different name or
669 * differentiate a non-standard port. This is useful for ssh
670 * tunneling over forwarded connections or if you run multiple
671 * sshd's on different ports on the same machine.
672 */
673 if (hostfile_hostname != NULL) {
674 if (options.host_key_alias != NULL) {
675 *hostfile_hostname = xstrdup(options.host_key_alias);
676 debug("using hostkeyalias: %s", *hostfile_hostname);
677 } else {
678 *hostfile_hostname = put_host_port(hostname, port);
679 }
680 }
681}
682
610/* 683/*
611 * check whether the supplied host key is valid, return -1 if the key 684 * check whether the supplied host key is valid, return -1 if the key
612 * is not valid. the user_hostfile will not be updated if 'readonly' is true. 685 * is not valid. the user_hostfile will not be updated if 'readonly' is true.
@@ -616,21 +689,21 @@ check_host_cert(const char *host, const Key *host_key)
616#define ROQUIET 2 689#define ROQUIET 2
617static int 690static int
618check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, 691check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
619 Key *host_key, int readonly, const char *user_hostfile, 692 Key *host_key, int readonly, char *user_hostfile,
620 const char *system_hostfile) 693 char *system_hostfile)
621{ 694{
622 Key *file_key, *raw_key = NULL; 695 Key *raw_key = NULL;
623 const char *type; 696 const char *type;
624 char *ip = NULL, *host = NULL; 697 char *ip = NULL, *host = NULL;
625 char hostline[1000], *hostp, *fp, *ra; 698 char hostline[1000], *hostp, *fp, *ra;
626 HostStatus host_status; 699 HostStatus host_status;
627 HostStatus ip_status; 700 HostStatus ip_status;
628 int r, want_cert, local = 0, host_ip_differ = 0; 701 int r, want_cert = key_is_cert(host_key), host_ip_differ = 0;
629 int salen; 702 int local = sockaddr_is_local(hostaddr);
630 char ntop[NI_MAXHOST];
631 char msg[1024]; 703 char msg[1024];
632 int len, host_line, ip_line, cancelled_forwarding = 0; 704 int len, cancelled_forwarding = 0;
633 const char *host_file = NULL, *ip_file = NULL; 705 struct hostkeys *host_hostkeys, *ip_hostkeys;
706 const struct hostkey_entry *host_found, *ip_found;
634 707
635 /* 708 /*
636 * Force accepting of the host key for loopback/localhost. The 709 * Force accepting of the host key for loopback/localhost. The
@@ -640,23 +713,6 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
640 * essentially disables host authentication for localhost; however, 713 * essentially disables host authentication for localhost; however,
641 * this is probably not a real problem. 714 * this is probably not a real problem.
642 */ 715 */
643 /** hostaddr == 0! */
644 switch (hostaddr->sa_family) {
645 case AF_INET:
646 local = (ntohl(((struct sockaddr_in *)hostaddr)->
647 sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
648 salen = sizeof(struct sockaddr_in);
649 break;
650 case AF_INET6:
651 local = IN6_IS_ADDR_LOOPBACK(
652 &(((struct sockaddr_in6 *)hostaddr)->sin6_addr));
653 salen = sizeof(struct sockaddr_in6);
654 break;
655 default:
656 local = 0;
657 salen = sizeof(struct sockaddr_storage);
658 break;
659 }
660 if (options.no_host_authentication_for_localhost == 1 && local && 716 if (options.no_host_authentication_for_localhost == 1 && local &&
661 options.host_key_alias == NULL) { 717 options.host_key_alias == NULL) {
662 debug("Forcing accepting of host key for " 718 debug("Forcing accepting of host key for "
@@ -665,17 +721,10 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
665 } 721 }
666 722
667 /* 723 /*
668 * We don't have the remote ip-address for connections 724 * Prepare the hostname and address strings used for hostkey lookup.
669 * using a proxy command 725 * In some cases, these will have a port number appended.
670 */ 726 */
671 if (options.proxy_command == NULL) { 727 get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip);
672 if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop),
673 NULL, 0, NI_NUMERICHOST) != 0)
674 fatal("check_host_key: getnameinfo failed");
675 ip = put_host_port(ntop, port);
676 } else {
677 ip = xstrdup("<no hostip for proxy command>");
678 }
679 728
680 /* 729 /*
681 * Turn off check_host_ip if the connection is to localhost, via proxy 730 * Turn off check_host_ip if the connection is to localhost, via proxy
@@ -685,74 +734,52 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
685 strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) 734 strcmp(hostname, ip) == 0 || options.proxy_command != NULL))
686 options.check_host_ip = 0; 735 options.check_host_ip = 0;
687 736
688 /* 737 host_hostkeys = init_hostkeys();
689 * Allow the user to record the key under a different name or 738 load_hostkeys(host_hostkeys, host, user_hostfile);
690 * differentiate a non-standard port. This is useful for ssh 739 load_hostkeys(host_hostkeys, host, system_hostfile);
691 * tunneling over forwarded connections or if you run multiple 740
692 * sshd's on different ports on the same machine. 741 ip_hostkeys = NULL;
693 */ 742 if (!want_cert && options.check_host_ip) {
694 if (options.host_key_alias != NULL) { 743 ip_hostkeys = init_hostkeys();
695 host = xstrdup(options.host_key_alias); 744 load_hostkeys(ip_hostkeys, ip, user_hostfile);
696 debug("using hostkeyalias: %s", host); 745 load_hostkeys(ip_hostkeys, ip, system_hostfile);
697 } else {
698 host = put_host_port(hostname, port);
699 } 746 }
700 747
701 retry: 748 retry:
749 /* Reload these as they may have changed on cert->key downgrade */
702 want_cert = key_is_cert(host_key); 750 want_cert = key_is_cert(host_key);
703 type = key_type(host_key); 751 type = key_type(host_key);
704 752
705 /* 753 /*
706 * Store the host key from the known host file in here so that we can
707 * compare it with the key for the IP address.
708 */
709 file_key = key_new(key_is_cert(host_key) ? KEY_UNSPEC : host_key->type);
710
711 /*
712 * Check if the host key is present in the user's list of known 754 * Check if the host key is present in the user's list of known
713 * hosts or in the systemwide list. 755 * hosts or in the systemwide list.
714 */ 756 */
715 host_file = user_hostfile; 757 host_status = check_key_in_hostkeys(host_hostkeys, host_key,
716 host_status = check_host_in_hostfile(host_file, host, host_key, 758 &host_found);
717 file_key, &host_line); 759
718 if (host_status == HOST_NEW) {
719 host_file = system_hostfile;
720 host_status = check_host_in_hostfile(host_file, host, host_key,
721 file_key, &host_line);
722 }
723 /* 760 /*
724 * Also perform check for the ip address, skip the check if we are 761 * Also perform check for the ip address, skip the check if we are
725 * localhost, looking for a certificate, or the hostname was an ip 762 * localhost, looking for a certificate, or the hostname was an ip
726 * address to begin with. 763 * address to begin with.
727 */ 764 */
728 if (!want_cert && options.check_host_ip) { 765 if (!want_cert && ip_hostkeys != NULL) {
729 Key *ip_key = key_new(host_key->type); 766 ip_status = check_key_in_hostkeys(ip_hostkeys, host_key,
730 767 &ip_found);
731 ip_file = user_hostfile;
732 ip_status = check_host_in_hostfile(ip_file, ip, host_key,
733 ip_key, &ip_line);
734 if (ip_status == HOST_NEW) {
735 ip_file = system_hostfile;
736 ip_status = check_host_in_hostfile(ip_file, ip,
737 host_key, ip_key, &ip_line);
738 }
739 if (host_status == HOST_CHANGED && 768 if (host_status == HOST_CHANGED &&
740 (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) 769 (ip_status != HOST_CHANGED ||
770 (ip_found != NULL &&
771 !key_equal(ip_found->key, host_found->key))))
741 host_ip_differ = 1; 772 host_ip_differ = 1;
742
743 key_free(ip_key);
744 } else 773 } else
745 ip_status = host_status; 774 ip_status = host_status;
746 775
747 key_free(file_key);
748
749 switch (host_status) { 776 switch (host_status) {
750 case HOST_OK: 777 case HOST_OK:
751 /* The host is known and the key matches. */ 778 /* The host is known and the key matches. */
752 debug("Host '%.200s' is known and matches the %s host %s.", 779 debug("Host '%.200s' is known and matches the %s host %s.",
753 host, type, want_cert ? "certificate" : "key"); 780 host, type, want_cert ? "certificate" : "key");
754 debug("Found %s in %s:%d", 781 debug("Found %s in %s:%lu", want_cert ? "CA key" : "key",
755 want_cert ? "CA key" : "key", host_file, host_line); 782 host_found->file, host_found->line);
756 if (want_cert && !check_host_cert(hostname, host_key)) 783 if (want_cert && !check_host_cert(hostname, host_key))
757 goto fail; 784 goto fail;
758 if (options.check_host_ip && ip_status == HOST_NEW) { 785 if (options.check_host_ip && ip_status == HOST_NEW) {
@@ -803,7 +830,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
803 } else if (options.strict_host_key_checking == 2) { 830 } else if (options.strict_host_key_checking == 2) {
804 char msg1[1024], msg2[1024]; 831 char msg1[1024], msg2[1024];
805 832
806 if (show_other_keys(host, host_key)) 833 if (show_other_keys(host_hostkeys, host_key))
807 snprintf(msg1, sizeof(msg1), 834 snprintf(msg1, sizeof(msg1),
808 "\nbut keys of different type are already" 835 "\nbut keys of different type are already"
809 " known for this host."); 836 " known for this host.");
@@ -844,8 +871,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
844 * local known_hosts file. 871 * local known_hosts file.
845 */ 872 */
846 if (options.check_host_ip && ip_status == HOST_NEW) { 873 if (options.check_host_ip && ip_status == HOST_NEW) {
847 snprintf(hostline, sizeof(hostline), "%s,%s", 874 snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
848 host, ip);
849 hostp = hostline; 875 hostp = hostline;
850 if (options.hash_known_hosts) { 876 if (options.hash_known_hosts) {
851 /* Add hash of host and IP separately */ 877 /* Add hash of host and IP separately */
@@ -899,8 +925,8 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
899 * all hosts that one might visit. 925 * all hosts that one might visit.
900 */ 926 */
901 debug("Host certificate authority does not " 927 debug("Host certificate authority does not "
902 "match %s in %s:%d", CA_MARKER, 928 "match %s in %s:%lu", CA_MARKER,
903 host_file, host_line); 929 host_found->file, host_found->line);
904 goto fail; 930 goto fail;
905 } 931 }
906 if (readonly == ROQUIET) 932 if (readonly == ROQUIET)
@@ -922,13 +948,15 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
922 error("DNS SPOOFING is happening or the IP address for the host"); 948 error("DNS SPOOFING is happening or the IP address for the host");
923 error("and its host key have changed at the same time."); 949 error("and its host key have changed at the same time.");
924 if (ip_status != HOST_NEW) 950 if (ip_status != HOST_NEW)
925 error("Offending key for IP in %s:%d", ip_file, ip_line); 951 error("Offending key for IP in %s:%lu",
952 ip_found->file, ip_found->line);
926 } 953 }
927 /* The host key has changed. */ 954 /* The host key has changed. */
928 warn_changed_key(host_key); 955 warn_changed_key(host_key);
929 error("Add correct host key in %.100s to get rid of this message.", 956 error("Add correct host key in %.100s to get rid of this message.",
930 user_hostfile); 957 user_hostfile);
931 error("Offending key in %s:%d", host_file, host_line); 958 error("Offending %s key in %s:%lu", key_type(host_found->key),
959 host_found->file, host_found->line);
932 960
933 /* 961 /*
934 * If strict host key checking is in use, the user will have 962 * If strict host key checking is in use, the user will have
@@ -1013,13 +1041,13 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
1013 snprintf(msg, sizeof(msg), 1041 snprintf(msg, sizeof(msg),
1014 "Warning: the %s host key for '%.200s' " 1042 "Warning: the %s host key for '%.200s' "
1015 "differs from the key for the IP address '%.128s'" 1043 "differs from the key for the IP address '%.128s'"
1016 "\nOffending key for IP in %s:%d", 1044 "\nOffending key for IP in %s:%lu",
1017 type, host, ip, ip_file, ip_line); 1045 type, host, ip, ip_found->file, ip_found->line);
1018 if (host_status == HOST_OK) { 1046 if (host_status == HOST_OK) {
1019 len = strlen(msg); 1047 len = strlen(msg);
1020 snprintf(msg + len, sizeof(msg) - len, 1048 snprintf(msg + len, sizeof(msg) - len,
1021 "\nMatching host key in %s:%d", 1049 "\nMatching host key in %s:%lu",
1022 host_file, host_line); 1050 host_found->file, host_found->line);
1023 } 1051 }
1024 if (options.strict_host_key_checking == 1) { 1052 if (options.strict_host_key_checking == 1) {
1025 logit("%s", msg); 1053 logit("%s", msg);
@@ -1037,6 +1065,10 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
1037 1065
1038 xfree(ip); 1066 xfree(ip);
1039 xfree(host); 1067 xfree(host);
1068 if (host_hostkeys != NULL)
1069 free_hostkeys(host_hostkeys);
1070 if (ip_hostkeys != NULL)
1071 free_hostkeys(ip_hostkeys);
1040 return 0; 1072 return 0;
1041 1073
1042fail: 1074fail:
@@ -1056,6 +1088,10 @@ fail:
1056 key_free(raw_key); 1088 key_free(raw_key);
1057 xfree(ip); 1089 xfree(ip);
1058 xfree(host); 1090 xfree(host);
1091 if (host_hostkeys != NULL)
1092 free_hostkeys(host_hostkeys);
1093 if (ip_hostkeys != NULL)
1094 free_hostkeys(ip_hostkeys);
1059 return -1; 1095 return -1;
1060} 1096}
1061 1097
@@ -1065,6 +1101,11 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
1065{ 1101{
1066 struct stat st; 1102 struct stat st;
1067 int flags = 0; 1103 int flags = 0;
1104 char *fp;
1105
1106 fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
1107 debug("Server host key: %s %s", key_type(host_key), fp);
1108 xfree(fp);
1068 1109
1069 /* XXX certs are not yet supported for DNS */ 1110 /* XXX certs are not yet supported for DNS */
1070 if (!key_is_cert(host_key) && options.verify_host_key_dns && 1111 if (!key_is_cert(host_key) && options.verify_host_key_dns &&
@@ -1108,7 +1149,7 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
1108 */ 1149 */
1109void 1150void
1110ssh_login(Sensitive *sensitive, const char *orighost, 1151ssh_login(Sensitive *sensitive, const char *orighost,
1111 struct sockaddr *hostaddr, struct passwd *pw, int timeout_ms) 1152 struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms)
1112{ 1153{
1113 char *host, *cp; 1154 char *host, *cp;
1114 char *server_user, *local_user; 1155 char *server_user, *local_user;
@@ -1131,7 +1172,7 @@ ssh_login(Sensitive *sensitive, const char *orighost,
1131 /* key exchange */ 1172 /* key exchange */
1132 /* authenticate user */ 1173 /* authenticate user */
1133 if (compat20) { 1174 if (compat20) {
1134 ssh_kex2(host, hostaddr); 1175 ssh_kex2(host, hostaddr, port);
1135 ssh_userauth2(local_user, server_user, host, sensitive); 1176 ssh_userauth2(local_user, server_user, host, sensitive);
1136 } else { 1177 } else {
1137 ssh_kex(host, hostaddr); 1178 ssh_kex(host, hostaddr);
@@ -1158,61 +1199,35 @@ ssh_put_password(char *password)
1158 xfree(padded); 1199 xfree(padded);
1159} 1200}
1160 1201
1161static int
1162show_key_from_file(const char *file, const char *host, int keytype)
1163{
1164 Key *found;
1165 char *fp, *ra;
1166 int line, ret;
1167
1168 found = key_new(keytype);
1169 if ((ret = lookup_key_in_hostfile_by_type(file, host,
1170 keytype, found, &line))) {
1171 fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
1172 ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART);
1173 logit("WARNING: %s key found for host %s\n"
1174 "in %s:%d\n"
1175 "%s key fingerprint %s.\n%s\n",
1176 key_type(found), host, file, line,
1177 key_type(found), fp, ra);
1178 xfree(ra);
1179 xfree(fp);
1180 }
1181 key_free(found);
1182 return (ret);
1183}
1184
1185/* print all known host keys for a given host, but skip keys of given type */ 1202/* print all known host keys for a given host, but skip keys of given type */
1186static int 1203static int
1187show_other_keys(const char *host, Key *key) 1204show_other_keys(struct hostkeys *hostkeys, Key *key)
1188{ 1205{
1189 int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1}; 1206 int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1};
1190 int i, found = 0; 1207 int i, ret = 0;
1208 char *fp, *ra;
1209 const struct hostkey_entry *found;
1191 1210
1192 for (i = 0; type[i] != -1; i++) { 1211 for (i = 0; type[i] != -1; i++) {
1193 if (type[i] == key->type) 1212 if (type[i] == key->type)
1194 continue; 1213 continue;
1195 if (type[i] != KEY_RSA1 && 1214 if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found))
1196 show_key_from_file(options.user_hostfile2, host, type[i])) {
1197 found = 1;
1198 continue;
1199 }
1200 if (type[i] != KEY_RSA1 &&
1201 show_key_from_file(options.system_hostfile2, host, type[i])) {
1202 found = 1;
1203 continue;
1204 }
1205 if (show_key_from_file(options.user_hostfile, host, type[i])) {
1206 found = 1;
1207 continue; 1215 continue;
1208 } 1216 fp = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_HEX);
1209 if (show_key_from_file(options.system_hostfile, host, type[i])) { 1217 ra = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_RANDOMART);
1210 found = 1; 1218 logit("WARNING: %s key found for host %s\n"
1211 continue; 1219 "in %s:%lu\n"
1212 } 1220 "%s key fingerprint %s.",
1213 debug2("no key of type %d for host %s", type[i], host); 1221 key_type(found->key),
1222 found->host, found->file, found->line,
1223 key_type(found->key), fp);
1224 if (options.visual_host_key)
1225 logit("%s", ra);
1226 xfree(ra);
1227 xfree(fp);
1228 ret = 1;
1214 } 1229 }
1215 return (found); 1230 return ret;
1216} 1231}
1217 1232
1218static void 1233static void