diff options
Diffstat (limited to 'source3')
-rw-r--r-- | source3/lib/interface.c | 6 | ||||
-rw-r--r-- | source3/lib/system.c | 43 | ||||
-rw-r--r-- | source3/lib/util.c | 105 | ||||
-rw-r--r-- | source3/lib/util_sock.c | 337 | ||||
-rw-r--r-- | source3/libsmb/namequery.c | 60 | ||||
-rw-r--r-- | source3/nsswitch/winbind_krb5_locator.c | 3 |
6 files changed, 314 insertions, 240 deletions
diff --git a/source3/lib/interface.c b/source3/lib/interface.c index ded70683e0..d2aa69a289 100644 --- a/source3/lib/interface.c +++ b/source3/lib/interface.c @@ -461,7 +461,7 @@ static void interpret_interface(char *token) /* maybe it is a DNS name */ p = strchr_m(token,'/'); if (p == NULL) { - if (!interpret_string_addr(&ss, token)) { + if (!interpret_string_addr(&ss, token, 0)) { DEBUG(2, ("interpret_interface: Can't find address " "for %s\n", token)); return; @@ -481,7 +481,7 @@ static void interpret_interface(char *token) /* parse it into an IP address/netmasklength pair */ *p = 0; - goodaddr = interpret_string_addr(&ss, token); + goodaddr = interpret_string_addr(&ss, token, 0); *p++ = '/'; if (!goodaddr) { @@ -492,7 +492,7 @@ static void interpret_interface(char *token) } if (strlen(p) > 2) { - goodaddr = interpret_string_addr(&ss_mask, p); + goodaddr = interpret_string_addr(&ss_mask, p, 0); if (!goodaddr) { DEBUG(2,("interpret_interface: " "can't determine netmask from %s\n", diff --git a/source3/lib/system.c b/source3/lib/system.c index bc7de84767..bf35722ea5 100644 --- a/source3/lib/system.c +++ b/source3/lib/system.c @@ -682,49 +682,6 @@ int sys_chroot(const char *dname) #endif } -/************************************************************************** -A wrapper for gethostbyname() that tries avoids looking up hostnames -in the root domain, which can cause dial-on-demand links to come up for no -apparent reason. -****************************************************************************/ - -struct hostent *sys_gethostbyname(const char *name) -{ -#ifdef REDUCE_ROOT_DNS_LOOKUPS - char query[HOST_NAME_MAX], hostname[HOST_NAME_MAX]; - char *domain; - - /* Does this name have any dots in it? If so, make no change */ - - if (strchr_m(name, '.')) - return(gethostbyname(name)); - - /* Get my hostname, which should have domain name - attached. If not, just do the gethostname on the - original string. - */ - - gethostname(hostname, sizeof(hostname) - 1); - hostname[sizeof(hostname) - 1] = 0; - if ((domain = strchr_m(hostname, '.')) == NULL) - return(gethostbyname(name)); - - /* Attach domain name to query and do modified query. - If names too large, just do gethostname on the - original string. - */ - - if((strlen(name) + strlen(domain)) >= sizeof(query)) - return(gethostbyname(name)); - - slprintf(query, sizeof(query)-1, "%s%s", name, domain); - return(gethostbyname(query)); -#else /* REDUCE_ROOT_DNS_LOOKUPS */ - return(gethostbyname(name)); -#endif /* REDUCE_ROOT_DNS_LOOKUPS */ -} - - #if defined(HAVE_POSIX_CAPABILITIES) #ifdef HAVE_SYS_CAPABILITY_H diff --git a/source3/lib/util.c b/source3/lib/util.c index f457e53c47..b25190b2f7 100644 --- a/source3/lib/util.c +++ b/source3/lib/util.c @@ -1209,37 +1209,6 @@ BOOL get_myname(char *my_name) } /**************************************************************************** - Get my own canonical name, including domain. -****************************************************************************/ - -BOOL get_mydnsfullname(fstring my_dnsname) -{ - static fstring dnshostname; - struct hostent *hp; - - if (!*dnshostname) { - /* get my host name */ - if (gethostname(dnshostname, sizeof(dnshostname)) == -1) { - *dnshostname = '\0'; - DEBUG(0,("gethostname failed\n")); - return False; - } - - /* Ensure null termination. */ - dnshostname[sizeof(dnshostname)-1] = '\0'; - - /* Ensure we get the cannonical name. */ - if (!(hp = sys_gethostbyname(dnshostname))) { - *dnshostname = '\0'; - return False; - } - fstrcpy(dnshostname, hp->h_name); - } - fstrcpy(my_dnsname, dnshostname); - return True; -} - -/**************************************************************************** Get my own domain name. ****************************************************************************/ @@ -2742,48 +2711,48 @@ BOOL unix_wild_match(const char *pattern, const char *string) /********************************************************************** Converts a name to a fully qualified domain name. - Returns True if lookup succeeded, False if not (then fqdn is set to name) + Returns true if lookup succeeded, false if not (then fqdn is set to name) + Note we deliberately use gethostbyname here, not getaddrinfo as we want + to examine the h_aliases and I don't know how to do that with getaddrinfo. ***********************************************************************/ - -BOOL name_to_fqdn(fstring fqdn, const char *name) -{ - struct hostent *hp = sys_gethostbyname(name); - - if ( hp && hp->h_name && *hp->h_name ) { - char *full = NULL; - - /* find out if the fqdn is returned as an alias - * to cope with /etc/hosts files where the first - * name is not the fqdn but the short name */ - if (hp->h_aliases && (! strchr_m(hp->h_name, '.'))) { - int i; - for (i = 0; hp->h_aliases[i]; i++) { - if (strchr_m(hp->h_aliases[i], '.')) { - full = hp->h_aliases[i]; - break; - } - } - } - if (full && (StrCaseCmp(full, "localhost.localdomain") == 0)) { - DEBUG(1, ("WARNING: your /etc/hosts file may be broken!\n")); - DEBUGADD(1, (" Specifing the machine hostname for address 127.0.0.1 may lead\n")); - DEBUGADD(1, (" to Kerberos authentication problems as localhost.localdomain\n")); - DEBUGADD(1, (" may end up being used instead of the real machine FQDN.\n")); - full = hp->h_name; - } - - if (!full) { - full = hp->h_name; - } - DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, full)); - fstrcpy(fqdn, full); - return True; - } else { +bool name_to_fqdn(fstring fqdn, const char *name) +{ + char *full = NULL; + struct hostent *hp = gethostbyname(name); + + if (!hp || !hp->h_name || !*hp->h_name) { DEBUG(10,("name_to_fqdn: lookup for %s failed.\n", name)); fstrcpy(fqdn, name); - return False; + return false; } + + /* Find out if the fqdn is returned as an alias + * to cope with /etc/hosts files where the first + * name is not the fqdn but the short name */ + if (hp->h_aliases && (! strchr_m(hp->h_name, '.'))) { + int i; + for (i = 0; hp->h_aliases[i]; i++) { + if (strchr_m(hp->h_aliases[i], '.')) { + full = hp->h_aliases[i]; + break; + } + } + } + if (full && (StrCaseCmp(full, "localhost.localdomain") == 0)) { + DEBUG(1, ("WARNING: your /etc/hosts file may be broken!\n")); + DEBUGADD(1, (" Specifing the machine hostname for address 127.0.0.1 may lead\n")); + DEBUGADD(1, (" to Kerberos authentication problems as localhost.localdomain\n")); + DEBUGADD(1, (" may end up being used instead of the real machine FQDN.\n")); + full = hp->h_name; + } + if (!full) { + full = hp->h_name; + } + + DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, full)); + fstrcpy(fqdn, full); + return true; } /********************************************************************** diff --git a/source3/lib/util_sock.c b/source3/lib/util_sock.c index 8079932620..eb3b35339c 100644 --- a/source3/lib/util_sock.c +++ b/source3/lib/util_sock.c @@ -29,63 +29,133 @@ static int client_fd = -1; static char client_ip_string[INET6_ADDRSTRLEN]; /**************************************************************************** - Return true if a string could be a pure IPv4 address. + Return true if a string could be an IPv4 address. ****************************************************************************/ bool is_ipaddress_v4(const char *str) { - bool pure_address = true; - int i; + int ret = -1; + struct in_addr dest; - for (i=0; pure_address && str[i]; i++) { - if (!(isdigit((int)str[i]) || str[i] == '.')) { - pure_address = false; - } + ret = inet_pton(AF_INET, str, &dest); + if (ret > 0) { + return true; } + return false; +} - /* Check that a pure number is not misinterpreted as an IP */ - pure_address = pure_address && (strchr_m(str, '.') != NULL); - return pure_address; +/**************************************************************************** + Return true if a string could be an IPv4 or IPv6 address. +****************************************************************************/ + +bool is_ipaddress(const char *str) +{ + int ret = -1; + +#if defined(AF_INET6) + struct in6_addr dest6; + + ret = inet_pton(AF_INET6, str, &dest6); + if (ret > 0) { + return true; + } +#endif + return is_ipaddress_v4(str); +} + +/******************************************************************* + Wrap getaddrinfo... +******************************************************************/ + +static bool interpret_string_addr_internal(struct addrinfo **ppres, + const char *str, int flags) +{ + int ret; + struct addrinfo hints; + + memset(&hints, '\0', sizeof(hints)); + /* By default make sure it supports TCP. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = flags; + + ret = getaddrinfo(str, NULL, + &hints, + ppres); + if (ret) { + DEBUG(3,("interpret_string_addr_interal: getaddrinfo failed " + "for name %s [%s]\n", + str, + gai_strerror(ret) )); + return false; + } + return true; } /**************************************************************************** Interpret an internet address or name into an IP address in 4 byte form. + RETURNS IN NETWORK BYTE ORDER (big endian). ****************************************************************************/ uint32 interpret_addr(const char *str) { - struct hostent *hp; - uint32 res; + uint32 ret; - if (strcmp(str,"0.0.0.0") == 0) - return(0); - if (strcmp(str,"255.255.255.255") == 0) - return(0xFFFFFFFF); - - /* if it's in the form of an IP address then + /* If it's in the form of an IP address then * get the lib to interpret it */ if (is_ipaddress_v4(str)) { - res = inet_addr(str); + struct in_addr dest; + + if (inet_pton(AF_INET, str, &dest) <= 0) { + /* Error - this shouldn't happen ! */ + DEBUG(0,("interpret_addr: inet_pton failed " + "host %s\n", + str)); + return 0; + } + ret = dest.s_addr; /* NETWORK BYTE ORDER ! */ } else { - /* otherwise assume it's a network name of some sort and use - sys_gethostbyname */ - if ((hp = sys_gethostbyname(str)) == 0) { - DEBUG(3,("sys_gethostbyname: Unknown host. %s\n",str)); + /* Otherwise assume it's a network name of some sort and use + getadddrinfo. */ + struct addrinfo *res = NULL; + struct addrinfo *res_list = NULL; + if (!interpret_string_addr_internal(&res_list, + str, + AI_ADDRCONFIG)) { + DEBUG(3,("interpret_addr: Unknown host. %s\n",str)); return 0; } - if(hp->h_addr == NULL) { - DEBUG(3,("sys_gethostbyname: host address is " + /* Find the first IPv4 address. */ + for (res = res_list; res; res = res->ai_next) { + if (res->ai_family != AF_INET) { + continue; + } + if (res->ai_addr == NULL) { + continue; + } + break; + } + if(res == NULL) { + DEBUG(3,("interpret_addr: host address is " "invalid for host %s\n",str)); + if (res_list) { + freeaddrinfo(res_list); + } return 0; } - putip((char *)&res,(char *)hp->h_addr); + putip((char *)&ret, + &((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr); + if (res_list) { + freeaddrinfo(res_list); + } } - if (res == (uint32)-1) - return(0); + /* This is so bogus - all callers need fixing... JRA. */ + if (ret == (uint32)-1) { + return 0; + } - return(res); + return ret; } /******************************************************************* @@ -105,31 +175,20 @@ struct in_addr *interpret_addr2(const char *str) struct sockaddr_storage. ******************************************************************/ -bool interpret_string_addr(struct sockaddr_storage *pss, const char *str) +bool interpret_string_addr(struct sockaddr_storage *pss, + const char *str, + int flags) { - int ret; struct addrinfo *res = NULL; - struct addrinfo hints; memset(pss,'\0', sizeof(*pss)); - memset(&hints, '\0', sizeof(hints)); - /* By default make sure it supports TCP. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG; - - ret = getaddrinfo(str, NULL, - &hints, - &res); - - if (ret) { - DEBUG(3,("interpret_string_addr: getaddrinfo failed for " - "name %s [%s]\n", - str, - gai_strerror(ret) )); + if (!interpret_string_addr_internal(&res, str, flags|AI_ADDRCONFIG)) { + return false; + } + if (!res) { return false; } - /* Copy the first sockaddr. */ memcpy(pss, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); @@ -155,7 +214,8 @@ bool is_loopback_addr(const struct sockaddr_storage *pss) { #if defined(AF_INET6) if (pss->ss_family == AF_INET) { - struct in6_addr *pin6 = &((struct sockaddr_in6 *)pss)->sin6_addr; + struct in6_addr *pin6 = + &((struct sockaddr_in6 *)pss)->sin6_addr; return IN6_IS_ADDR_LOOPBACK(pin6); } #endif @@ -185,7 +245,8 @@ bool is_zero_addr(const struct sockaddr_storage *pss) { #if defined(AF_INET6) if (pss->ss_family == AF_INET) { - struct in6_addr *pin6 = &((struct sockaddr_in6 *)pss)->sin6_addr; + struct in6_addr *pin6 = + &((struct sockaddr_in6 *)pss)->sin6_addr; return IN6_IS_ADDR_UNSPECIFIED(pin6); } #endif @@ -1215,7 +1276,7 @@ bool send_smb(int fd, char *buffer) int open_socket_in(int type, int port, int dlevel, - uint32 socket_addr, + uint32 socket_addr, /* NETWORK BYTE ORDER */ bool rebind ) { struct sockaddr_in sock; @@ -1595,22 +1656,14 @@ static bool matchname(const char *remotehost, const struct sockaddr_storage *pss, socklen_t len) { - struct addrinfo hints; struct addrinfo *res = NULL; struct addrinfo *ailist = NULL; char addr_buf[INET6_ADDRSTRLEN]; - int ret = -1; - - memset(&hints,'\0',sizeof(struct addrinfo)); - /* By default make sure it supports TCP. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG|AI_CANONNAME; - - ret = getaddrinfo(remotehost, NULL, - &hints, - &res); + bool ret = interpret_string_addr_internal(&ailist, + remotehost, + AI_ADDRCONFIG|AI_CANONNAME); - if (ret || res == NULL) { + if (!ret || ailist == NULL) { DEBUG(3,("matchname: getaddrinfo failed for " "name %s [%s]\n", remotehost, @@ -1622,24 +1675,25 @@ static bool matchname(const char *remotehost, * Make sure that getaddrinfo() returns the "correct" host name. */ - if (res->ai_canonname == NULL || - (!strequal(remotehost, res->ai_canonname) && + if (ailist->ai_canonname == NULL || + (!strequal(remotehost, ailist->ai_canonname) && !strequal(remotehost, "localhost"))) { DEBUG(0,("matchname: host name/name mismatch: %s != %s\n", remotehost, - res->ai_canonname ? res->ai_canonname : "(NULL)")); - freeaddrinfo(res); + ailist->ai_canonname ? + ailist->ai_canonname : "(NULL)")); + freeaddrinfo(ailist); return false; } /* Look up the host address in the address list we just got. */ - for (ailist = res; ailist; ailist = ailist->ai_next) { - if (!ailist->ai_addr) { + for (res = ailist; res; res = res->ai_next) { + if (!res->ai_addr) { continue; } - if (addr_equal((const struct sockaddr_storage *)ailist->ai_addr, + if (addr_equal((const struct sockaddr_storage *)res->ai_addr, pss)) { - freeaddrinfo(res); + freeaddrinfo(ailist); return true; } } @@ -1655,9 +1709,11 @@ static bool matchname(const char *remotehost, sizeof(addr_buf), pss, len), - res->ai_canonname ? res->ai_canonname : "(NULL)")); + ailist->ai_canonname ? ailist->ai_canonname : "(NULL)")); - freeaddrinfo(res); + if (ailist) { + freeaddrinfo(ailist); + } return false; } @@ -1837,8 +1893,62 @@ out_umask: #endif /* HAVE_UNIXSOCKET */ } +/**************************************************************************** + Get my own canonical name, including domain. +****************************************************************************/ + +bool get_mydnsfullname(fstring my_dnsname) +{ + static fstring dnshostname; + + if (!*dnshostname) { + struct addrinfo *res = NULL; + bool ret; + + /* get my host name */ + if (gethostname(dnshostname, sizeof(dnshostname)) == -1) { + *dnshostname = '\0'; + DEBUG(0,("get_mydnsfullname: gethostname failed\n")); + return false; + } + + /* Ensure null termination. */ + dnshostname[sizeof(dnshostname)-1] = '\0'; + + ret = interpret_string_addr_internal(&res, + dnshostname, + AI_ADDRCONFIG|AI_CANONNAME); + + if (!ret || res == NULL) { + DEBUG(3,("get_mydnsfullname: getaddrinfo failed for " + "name %s [%s]\n", + dnshostname, + gai_strerror(ret) )); + return false; + } + + /* + * Make sure that getaddrinfo() returns the "correct" host name. + */ + + if (res->ai_canonname == NULL) { + DEBUG(3,("get_mydnsfullname: failed to get " + "canonical name for %s\n", + dnshostname)); + freeaddrinfo(res); + return false; + } + + + fstrcpy(dnshostname, res->ai_canonname); + freeaddrinfo(res); + } + fstrcpy(my_dnsname, dnshostname); + return true; +} + /************************************************************ - Is this my name ? Needs fixing for IPv6. + Is this my name ? ************************************************************/ bool is_myname_or_ipaddr(const char *s) @@ -1846,83 +1956,82 @@ bool is_myname_or_ipaddr(const char *s) fstring name, dnsname; char *servername; - if ( !s ) { + if (!s) { return false; } - /* santize the string from '\\name' */ + /* Santize the string from '\\name' */ + fstrcpy(name, s); - fstrcpy( name, s ); - - servername = strrchr_m( name, '\\' ); - if ( !servername ) + servername = strrchr_m(name, '\\' ); + if (!servername) { servername = name; - else + } else { servername++; + } - /* optimize for the common case */ - - if (strequal(servername, global_myname())) + /* Optimize for the common case */ + if (strequal(servername, global_myname())) { return true; + } - /* check for an alias */ - - if (is_myname(servername)) + /* Check for an alias */ + if (is_myname(servername)) { return true; + } - /* check for loopback */ - - if (strequal(servername, "127.0.0.1")) + /* Check for loopback */ + if (strequal(servername, "127.0.0.1") || + strequal(servername, "::1")) { return true; + } - if (strequal(servername, "localhost")) + if (strequal(servername, "localhost")) { return true; + } - /* maybe it's my dns name */ - - if ( get_mydnsfullname( dnsname ) ) - if ( strequal( servername, dnsname ) ) + /* Maybe it's my dns name */ + if (get_mydnsfullname(dnsname)) { + if (strequal(servername, dnsname)) { return true; + } + } - /* handle possible CNAME records */ - - if ( !is_ipaddress_v4( servername ) ) { - /* use DNS to resolve the name, but only the first address */ - struct hostent *hp; - - if (((hp = sys_gethostbyname(name)) != NULL) && (hp->h_addr != NULL)) { - struct in_addr return_ip; - putip( (char*)&return_ip, (char*)hp->h_addr ); - fstrcpy( name, inet_ntoa( return_ip ) ); + /* Handle possible CNAME records - convert to an IP addr. */ + if (!is_ipaddress(servername)) { + /* Use DNS to resolve the name, but only the first address */ + struct sockaddr_storage ss; + if (interpret_string_addr(&ss, servername,0)) { + print_sockaddr(name, + sizeof(name), + &ss, + sizeof(ss)); servername = name; } } - /* maybe its an IP address? */ - if (is_ipaddress_v4(servername)) { + /* Maybe its an IP address? */ + if (is_ipaddress(servername)) { struct sockaddr_storage ss; struct iface_struct nics[MAX_INTERFACES]; int i, n; - struct in_addr ip; - ip = *interpret_addr2(servername); - if (is_zero_ip_v4(ip) || is_loopback_ip_v4(ip)) { + if (!interpret_string_addr(&ss, servername, AI_NUMERICHOST)) { return false; } - in_addr_to_sockaddr_storage(&ss, ip); + if (is_zero_addr(&ss) || is_loopback_addr(&ss)) { + return false; + } n = get_interfaces(nics, MAX_INTERFACES); for (i=0; i<n; i++) { - if (nics[i].ip.ss_family != AF_INET) { - continue; - } if (addr_equal(&nics[i].ip, &ss)) { return true; } } } - /* no match */ + /* No match */ return false; } diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c index 182d3641f7..5459210c64 100644 --- a/source3/libsmb/namequery.c +++ b/source3/libsmb/namequery.c @@ -1028,8 +1028,12 @@ static NTSTATUS resolve_hosts(const char *name, int name_type, /* * "host" means do a localhost, or dns lookup. */ - struct hostent *hp; - + struct addrinfo hints; + struct addrinfo *ailist = NULL; + struct addrinfo *res = NULL; + int ret = -1; + int i = 0; + if ( name_type != 0x20 && name_type != 0x0) { DEBUG(5, ("resolve_hosts: not appropriate for name type <0x%x>\n", name_type)); return NT_STATUS_INVALID_PARAMETER; @@ -1039,18 +1043,54 @@ static NTSTATUS resolve_hosts(const char *name, int name_type, *return_count = 0; DEBUG(3,("resolve_hosts: Attempting host lookup for name %s<0x%x>\n", name, name_type)); - - if (((hp = sys_gethostbyname(name)) != NULL) && (hp->h_addr != NULL)) { + + ZERO_STRUCT(hints); + /* By default make sure it supports TCP. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + ret = getaddrinfo(name, + NULL, + &hints, + &ailist); + if (ret) { + DEBUG(3,("resolve_hosts: getaddrinfo failed for name %s [%s]\n", + name, + gai_strerror(ret) )); + } + + for (res = ailist; res; res = res->ai_next) { struct in_addr return_ip; - putip((char *)&return_ip,(char *)hp->h_addr); - *return_iplist = SMB_MALLOC_P(struct ip_service); - if(*return_iplist == NULL) { + + /* IPv4 only for now until I convert ip_service */ + if (res->ai_family != AF_INET) { + continue; + } + if (!res->ai_addr) { + continue; + } + + putip((char *)&return_ip, + &((struct sockaddr_in *)res->ai_addr)->sin_addr); + + *return_count += 1; + i++; + + *return_iplist = SMB_REALLOC_ARRAY(*return_iplist, + struct ip_service, + *return_count); + if (!*return_iplist) { DEBUG(3,("resolve_hosts: malloc fail !\n")); + freeaddrinfo(ailist); return NT_STATUS_NO_MEMORY; } - (*return_iplist)->ip = return_ip; - (*return_iplist)->port = PORT_NONE; - *return_count = 1; + (*return_iplist)[i].ip = return_ip; + (*return_iplist)[i].port = PORT_NONE; + } + if (ailist) { + freeaddrinfo(ailist); + } + if (*return_count) { return NT_STATUS_OK; } return NT_STATUS_UNSUCCESSFUL; diff --git a/source3/nsswitch/winbind_krb5_locator.c b/source3/nsswitch/winbind_krb5_locator.c index 18a9fe3429..dc2664b67c 100644 --- a/source3/nsswitch/winbind_krb5_locator.c +++ b/source3/nsswitch/winbind_krb5_locator.c @@ -171,7 +171,7 @@ static krb5_error_code smb_krb5_locator_call_cbfunc(const char *name, int (*cbfunc)(void *, int, struct sockaddr *), void *cbdata) { - struct addrinfo *out; + struct addrinfo *out = NULL; int ret; int count = 3; @@ -206,7 +206,6 @@ static krb5_error_code smb_krb5_locator_call_cbfunc(const char *name, #endif freeaddrinfo(out); - return ret; } |