/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_LIB_ALT "/usr/lib/smbsrv/libsmbex.so" static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int); extern int __multi_innetgr(); extern int __netdir_getbyaddr_nosrv(struct netconfig *, struct nd_hostservlist **, struct netbuf *); #define C2H(c) "0123456789ABCDEF"[(c)] #define H2C(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : \ ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \ '\0') #define DEFAULT_SBOX_SIZE 256 /* * * hexdump * * Simple hex dump display function. Displays nbytes of buffer in hex and * printable format. Non-printing characters are shown as '.'. It is safe * to pass a null pointer. Each line begins with the offset. If nbytes is * 0, the line will be blank except for the offset. Example output: * * 00000000 54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61 This is a progra * 00000010 6D 20 74 65 73 74 2E 00 m test.. * */ void hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start) { static char *hex = "0123456789ABCDEF"; int i, count; int offset; unsigned char *p; char ascbuf[64]; char hexbuf[64]; char *ap = ascbuf; char *hp = hexbuf; if ((p = buffer) == NULL) return; offset = *start; *ap = '\0'; *hp = '\0'; count = 0; for (i = 0; i < nbytes; ++i) { if (i && (i % 16) == 0) { smb_tracef("%06X %s %s", offset, hexbuf, ascbuf); ap = ascbuf; hp = hexbuf; count = 0; offset += 16; } ap += sprintf(ap, "%c", (*p >= 0x20 && *p < 0x7F) ? *p : '.'); hp += sprintf(hp, " %c%c", hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]); ++p; ++count; } if (count) { smb_tracef("%06X %-48s %s", offset, hexbuf, ascbuf); offset += count; } *start = offset; } void hexdump(unsigned char *buffer, int nbytes) { unsigned long start = 0; hexdump_offset(buffer, nbytes, &start); } /* * bintohex * * Converts the given binary data (srcbuf) to * its equivalent hex chars (hexbuf). * * hexlen should be at least twice as srclen. * if hexbuf is not big enough returns 0. * otherwise returns number of valid chars in * hexbuf which is srclen * 2. */ size_t bintohex(const char *srcbuf, size_t srclen, char *hexbuf, size_t hexlen) { size_t outlen; char c; outlen = srclen << 1; if (hexlen < outlen) return (0); while (srclen-- > 0) { c = *srcbuf++; *hexbuf++ = C2H(c & 0xF); *hexbuf++ = C2H((c >> 4) & 0xF); } return (outlen); } /* * hextobin * * Converts hex to binary. * * Assuming hexbuf only contains hex digits (chars) * this function convert every two bytes of hexbuf * to one byte and put it in dstbuf. * * hexlen should be an even number. * dstlen should be at least half of hexlen. * * Returns 0 if sizes are not correct, otherwise * returns the number of converted bytes in dstbuf * which is half of hexlen. */ size_t hextobin(const char *hexbuf, size_t hexlen, char *dstbuf, size_t dstlen) { size_t outlen; if ((hexlen % 2) != 0) return (0); outlen = hexlen >> 1; if (dstlen < outlen) return (0); while (hexlen > 0) { *dstbuf = H2C(*hexbuf) & 0x0F; hexbuf++; *dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0; hexbuf++; hexlen -= 2; } return (outlen); } /* * Trim leading and trailing characters in the set defined by class * from a buffer containing a null-terminated string. * For example, if the input buffer contained "ABtext23" and class * contains "ABC123", the buffer will contain "text" on return. * * This function modifies the contents of buf in place and returns * a pointer to buf. */ char * strtrim(char *buf, const char *class) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); p += strspn(p, class); if (p != buf) { while ((*q = *p++) != '\0') ++q; } while (q != buf) { --q; if (strspn(q, class) == 0) return (buf); *q = '\0'; } return (buf); } /* * Strip the characters in the set defined by class from a buffer * containing a null-terminated string. * For example, if the input buffer contained "XYA 1textZ string3" * and class contains "123XYZ", the buffer will contain "A text string" * on return. * * This function modifies the contents of buf in place and returns * a pointer to buf. */ char * strstrip(char *buf, const char *class) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); while (*p) { p += strspn(p, class); *q++ = *p++; } *q = '\0'; return (buf); } /* * trim_whitespace * * Trim leading and trailing whitespace chars (as defined by isspace) * from a buffer. Example; if the input buffer contained " text ", * it will contain "text", when we return. We assume that the buffer * contains a null terminated string. A pointer to the buffer is * returned. */ char * trim_whitespace(char *buf) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); while (*p && isspace(*p)) ++p; while ((*q = *p++) != 0) ++q; if (q != buf) { while ((--q, isspace(*q)) != 0) *q = '\0'; } return (buf); } /* * This is the hash mechanism used to encrypt passwords for commands like * SamrSetUserInformation. It uses a 256 byte s-box. */ void rand_hash( unsigned char *data, size_t datalen, unsigned char *key, size_t keylen) { unsigned char sbox[DEFAULT_SBOX_SIZE]; unsigned char tmp; unsigned char index_i = 0; unsigned char index_j = 0; unsigned char j = 0; int i; for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) sbox[i] = (unsigned char)i; for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) { j += (sbox[i] + key[i % keylen]); tmp = sbox[i]; sbox[i] = sbox[j]; sbox[j] = tmp; } for (i = 0; i < datalen; ++i) { index_i++; index_j += sbox[index_i]; tmp = sbox[index_i]; sbox[index_i] = sbox[index_j]; sbox[index_j] = tmp; tmp = sbox[index_i] + sbox[index_j]; data[i] = data[i] ^ sbox[tmp]; } } /* * smb_chk_hostaccess * * Determines whether the specified host is in the given access list. * * We match on aliases of the hostname as well as on the canonical name. * Names in the access list may be either hosts or netgroups; they're * not distinguished syntactically. We check for hosts first because * it's cheaper (just M*N strcmp()s), then try netgroups. * * Function returns: * -1 for "all" (list is empty "" or "*") * 0 not found (host is not in the list or list is NULL) * 1 found * */ int smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list) { char addr[INET_ADDRSTRLEN]; char buff[256]; char *cstr = access_list, *gr = access_list; char *host; int clres; int i; int nentries = 0; int off; int response; int sbr = 0; struct nd_hostservlist *clnames; struct in_addr inaddr; struct sockaddr_in sa; struct sockaddr_in6 sa6; struct netbuf buf; struct netconfig *config; struct netent n, *np; if (access_list == NULL) return (0); /* If access list is empty or "*" - then it's "all" */ if (*access_list == '\0' || strcmp(access_list, "*") == 0) return (-1); switch (ipaddr->a_family) { case AF_INET: inaddr.s_addr = ipaddr->a_ipv4; sa.sin_family = AF_INET; sa.sin_port = 0; sa.sin_addr = inaddr; buf.len = buf.maxlen = sizeof (sa); buf.buf = (char *)&sa; config = getnetconfigent("tcp"); break; case AF_INET6: sa6.sin6_family = AF_INET6; sa6.sin6_port = 0; sa6.sin6_addr = ipaddr->a_ipv6; buf.len = buf.maxlen = sizeof (sa6); buf.buf = (char *)&sa6; config = getnetconfigent("tcp6"); break; default: return (1); } if (config == NULL) return (1); /* Try to lookup client hostname */ clres = __netdir_getbyaddr_nosrv(config, &clnames, &buf); freenetconfigent(config); for (;;) { if ((cstr = strpbrk(cstr, "[]:")) != NULL) { switch (*cstr) { case '[': case ']': sbr = !sbr; cstr++; continue; case ':': if (sbr) { cstr++; continue; } *cstr = '\0'; } } /* * If the list name has a '-' prepended then a match of * the following name implies failure instead of success. */ if (*gr == '-') { response = 0; gr++; } else { response = 1; } /* * First check if we have '@' entry, as it doesn't * require client hostname. */ if (*gr == '@') { gr++; if (!isdigit(*gr) && *gr != '[') { /* Netname support */ if ((np = getnetbyname_r(gr, &n, buff, sizeof (buff))) != NULL && np->n_net != 0) { while ((np->n_net & 0xFF000000u) == 0) np->n_net <<= 8; np->n_net = htonl(np->n_net); if (inet_ntop(AF_INET, &np->n_net, addr, INET_ADDRSTRLEN) == NULL) break; if (inet_matchaddr(buf.buf, addr) == 1) return (response); } } else { if (inet_matchaddr(buf.buf, gr) == 1) return (response); } if (cstr == NULL) break; gr = ++cstr; continue; } /* * No other checks can be performed if client address * can't be resolved. */ if (clres) { if (cstr == NULL) break; gr = ++cstr; continue; } /* Otherwise loop through all client hostname aliases */ for (i = 0; i < clnames->h_cnt; i++) { host = clnames->h_hostservs[i].h_host; /* * If the list name begins with a dot then * do a domain name suffix comparison. * A single dot matches any name with no * suffix. */ if (*gr == '.') { if (*(gr + 1) == '\0') { if (strchr(host, '.') == NULL) return (response); } else { off = strlen(host) - strlen(gr); if (off > 0 && strcasecmp(host + off, gr) == 0) { return (response); } } } else { /* Just do a hostname match */ if (strcasecmp(gr, host) == 0) return (response); } } nentries++; if (cstr == NULL) break; gr = ++cstr; } if (clres) return (0); return (smb_netgroup_match(clnames, access_list, nentries)); } /* * smb_netgroup_match * * Check whether any of the hostnames in clnames are * members (or non-members) of the netgroups in glist. * Since the innetgr lookup is rather expensive, the * result is cached. The cached entry is valid only * for VALID_TIME seconds. This works well because * typically these lookups occur in clusters when * a client is mounting. * * Note that this routine establishes a host membership * in a list of netgroups - we've no idea just which * netgroup in the list it is a member of. * * glist is a character array containing grc strings * representing netgroup names (optionally prefixed * with '-'). Each string is ended with '\0' and * followed immediately by the next string. */ static boolean_t smb_netgroup_match(struct nd_hostservlist *clnames, char *glist, int grc) { char **grl; char *gr; int nhosts = clnames->h_cnt; char *host; int i, j, n; boolean_t response; boolean_t belong = B_FALSE; static char *domain = NULL; if (domain == NULL) { int ssize; domain = malloc(SYS_NMLN); if (domain == NULL) return (B_FALSE); ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN); if (ssize > SYS_NMLN) { free(domain); domain = malloc(ssize); if (domain == NULL) return (B_FALSE); ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize); } /* Check for error in syscall or NULL domain name */ if (ssize <= 1) return (B_FALSE); } grl = calloc(grc, sizeof (char *)); if (grl == NULL) return (B_FALSE); for (i = 0, gr = glist; i < grc && !belong; ) { /* * If the netgroup name has a '-' prepended * then a match of this name implies a failure * instead of success. */ response = (*gr != '-') ? B_TRUE : B_FALSE; /* * Subsequent names with or without a '-' (but no mix) * can be grouped together for a single check. */ for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) { if ((response && *gr == '-') || (!response && *gr != '-')) break; grl[n] = response ? gr : gr + 1; } /* * Check the netgroup for each * of the hosts names (usually just one). */ for (j = 0; j < nhosts && !belong; j++) { host = clnames->h_hostservs[j].h_host; if (__multi_innetgr(n, grl, 1, &host, 0, NULL, 1, &domain)) belong = B_TRUE; } } free(grl); return (belong ? response : B_FALSE); } /* * Resolve the ZFS dataset from a path. * Returns, * 0 = On success. * -1 = Failure to open /etc/mnttab file or to get ZFS dataset. */ int smb_getdataset(libzfs_handle_t *libhdl, const char *path, char *dataset, size_t len) { char tmppath[MAXPATHLEN]; char *cp; FILE *fp; struct mnttab mnttab; struct mnttab mntpref; int rc = -1; /* * Optimisation: if the path is the default mountpoint then * the dataset name can be determined from path. * Attempt to open dataset by derived name and, if successful, * check if its mountpoint matches path. */ if (libhdl != NULL) { zfs_handle_t *hdl; char mountpnt[ZFS_MAXPROPLEN]; char *dsname = (char *)path + strspn(path, "/"); hdl = zfs_open(libhdl, dsname, ZFS_TYPE_FILESYSTEM); if (hdl != NULL) { if ((zfs_prop_get(hdl, ZFS_PROP_MOUNTPOINT, mountpnt, sizeof (mountpnt), NULL, NULL, 0, B_FALSE) == 0) && (strcmp(mountpnt, path) == 0)) { zfs_close(hdl); (void) strlcpy(dataset, dsname, len); return (0); } zfs_close(hdl); } } /* * Couldn't find a filesystem optimistically, use mnttab */ if ((fp = fopen(MNTTAB, "r")) == NULL) return (-1); (void) memset(&mnttab, '\0', sizeof (mnttab)); (void) strlcpy(tmppath, path, MAXPATHLEN); cp = tmppath; while (*cp != '\0') { resetmnttab(fp); (void) memset(&mntpref, '\0', sizeof (mntpref)); mntpref.mnt_mountp = tmppath; if (getmntany(fp, &mnttab, &mntpref) == 0) { if (mnttab.mnt_fstype == NULL) break; if (strcmp(mnttab.mnt_fstype, "zfs") != 0) break; /* * Ensure that there are no leading slashes * (required for zfs_open). */ cp = mnttab.mnt_special; cp += strspn(cp, "/"); (void) strlcpy(dataset, cp, len); rc = 0; break; } if (strcmp(tmppath, "/") == 0) break; if ((cp = strrchr(tmppath, '/')) == NULL) break; /* * The path has multiple components. * Remove the last component and try again. */ *cp = '\0'; if (tmppath[0] == '\0') (void) strcpy(tmppath, "/"); cp = tmppath; } (void) fclose(fp); return (rc); } /* * smb_dlopen * * Check to see if an interposer library exists. If it exists * and reports a valid version number and key (UUID), return * a handle to the library. Otherwise, return NULL. */ void * smb_dlopen(void) { uuid_t uuid; void *interposer_hdl; typedef int (*smbex_versionfn_t)(smbex_version_t *); smbex_versionfn_t getversion; smbex_version_t *version; bzero(&uuid, sizeof (uuid_t)); if (uuid_parse(SMBEX_KEY, uuid) < 0) return (NULL); interposer_hdl = dlopen(SMB_LIB_ALT, RTLD_NOW | RTLD_LOCAL); if (interposer_hdl == NULL) return (NULL); bzero(&getversion, sizeof (smbex_versionfn_t)); getversion = (smbex_versionfn_t)dlsym(interposer_hdl, "smbex_get_version"); if ((getversion == NULL) || (version = malloc(sizeof (smbex_version_t))) == NULL) { (void) dlclose(interposer_hdl); return (NULL); } bzero(version, sizeof (smbex_version_t)); if ((getversion(version) != 0) || (version->v_version != SMBEX_VERSION) || (uuid_compare(version->v_uuid, uuid) != 0)) { free(version); (void) dlclose(interposer_hdl); return (NULL); } free(version); return (interposer_hdl); } /* * smb_dlclose * * Closes handle to the interposed library. */ void smb_dlclose(void *handle) { if (handle) (void) dlclose(handle); } /* * This function is a wrapper for getnameinfo() to look up a hostname given an * IP address. The hostname returned by this function is used for constructing * the service principal name field of KRB AP-REQs. Hence, it should be * converted to lowercase for RFC 4120 section 6.2.1 conformance. */ int smb_getnameinfo(smb_inaddr_t *ip, char *hostname, int hostlen, int flags) { socklen_t salen; struct sockaddr_in6 sin6; struct sockaddr_in sin; void *sp; int rc; if (ip->a_family == AF_INET) { salen = sizeof (struct sockaddr_in); sin.sin_family = ip->a_family; sin.sin_port = 0; sin.sin_addr.s_addr = ip->a_ipv4; sp = &sin; } else { salen = sizeof (struct sockaddr_in6); sin6.sin6_family = ip->a_family; sin6.sin6_port = 0; (void) memcpy(&sin6.sin6_addr.s6_addr, &ip->a_ipv6, sizeof (sin6.sin6_addr.s6_addr)); sp = &sin6; } if ((rc = (getnameinfo((struct sockaddr *)sp, salen, hostname, hostlen, NULL, 0, flags))) == 0) (void) smb_strlwr(hostname); return (rc); } /* * A share name is considered invalid if it contains control * characters or any of the following characters (MSDN 236388). * * " / \ [ ] : | < > + ; , ? * = */ uint32_t smb_name_validate_share(const char *sharename) { const char *invalid = "\"/\\[]:|<>+;,?*="; const char *p; if (sharename == NULL) return (ERROR_INVALID_PARAMETER); if (strpbrk(sharename, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = sharename; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * User and group names are limited to 256 characters, cannot be terminated * by '.' and must not contain control characters or any of the following * characters. * * " / \ [ ] < > + ; , ? * = @ */ uint32_t smb_name_validate_account(const char *name) { const char *invalid = "\"/\\[]<>+;,?*=@"; const char *p; int len; if ((name == NULL) || (*name == '\0')) return (ERROR_INVALID_PARAMETER); len = strlen(name); if ((len > MAXNAMELEN) || (name[len - 1] == '.')) return (ERROR_INVALID_NAME); if (strpbrk(name, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = name; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Check a domain name for RFC 1035 and 1123 compliance. Domain names may * contain alphanumeric characters, hyphens and dots. The first and last * character of a label must be alphanumeric. Interior characters may be * alphanumeric or hypens. * * Domain names should not contain underscores but we allow them because * Windows names are often in non-compliance with this rule. */ uint32_t smb_name_validate_domain(const char *domain) { boolean_t new_label = B_TRUE; const char *p; char label_terminator; if (domain == NULL) return (ERROR_INVALID_PARAMETER); if (*domain == '\0') return (ERROR_INVALID_NAME); label_terminator = *domain; for (p = domain; *p != '\0'; ++p) { if (new_label) { if (!isalnum(*p)) return (ERROR_INVALID_NAME); new_label = B_FALSE; label_terminator = *p; continue; } if (*p == '.') { if (!isalnum(label_terminator)) return (ERROR_INVALID_NAME); new_label = B_TRUE; label_terminator = *p; continue; } label_terminator = *p; if (isalnum(*p) || *p == '-' || *p == '_') continue; return (ERROR_INVALID_NAME); } if (!isalnum(label_terminator)) return (ERROR_INVALID_NAME); return (ERROR_SUCCESS); } /* * A NetBIOS domain name can contain letters (a-zA-Z), numbers (0-9) and * hyphens. * * It cannot: * - be blank or longer than 15 chracters * - contain all numbers * - be the same as the computer name */ uint32_t smb_name_validate_nbdomain(const char *name) { char netbiosname[NETBIOS_NAME_SZ]; const char *p; int len; if (name == NULL) return (ERROR_INVALID_PARAMETER); len = strlen(name); if (len == 0 || len >= NETBIOS_NAME_SZ) return (ERROR_INVALID_NAME); if (strspn(name, "0123456789") == len) return (ERROR_INVALID_NAME); if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) { if (smb_strcasecmp(name, netbiosname, 0) == 0) return (ERROR_INVALID_NAME); } for (p = name; *p != '\0'; ++p) { if (isalnum(*p) || *p == '-' || *p == '_') continue; return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * A workgroup name can contain 1 to 15 characters but cannot be the same * as the NetBIOS name. The name must begin with a letter or number. * * The name cannot consist entirely of spaces or dots, which is covered * by the requirement that the name must begin with an alphanumeric * character. * * The name must not contain control characters or any of the following * characters. * * " / \ [ ] : | < > + = ; , ? */ uint32_t smb_name_validate_workgroup(const char *workgroup) { char netbiosname[NETBIOS_NAME_SZ]; const char *invalid = "\"/\\[]:|<>+=;,?"; const char *p; if (workgroup == NULL) return (ERROR_INVALID_PARAMETER); if (*workgroup == '\0' || (!isalnum(*workgroup))) return (ERROR_INVALID_NAME); if (strlen(workgroup) >= NETBIOS_NAME_SZ) return (ERROR_INVALID_NAME); if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) { if (smb_strcasecmp(workgroup, netbiosname, 0) == 0) return (ERROR_INVALID_NAME); } if (strpbrk(workgroup, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = workgroup; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Check for invalid characters in the given path. The list of invalid * characters includes control characters and the following: * * " / \ [ ] : | < > + ; , ? * = * * Since this is checking a path not each component, '/' is accepted * as separator not an invalid character, except as the first character * since this is supposed to be a relative path. */ uint32_t smb_name_validate_rpath(const char *relpath) { char *invalid = "\"\\[]:|<>+;,?*="; char *cp; if ((relpath == NULL) || (*relpath == '\0') || (*relpath == '/')) return (ERROR_INVALID_NAME); if (strpbrk(relpath, invalid)) return (ERROR_INVALID_NAME); for (cp = (char *)relpath; *cp != '\0'; cp++) { if (iscntrl(*cp)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Parse a string to obtain the account and domain names as separate strings. * * Names containing a backslash ('\') are known as qualified or composite * names. The string preceding the backslash should be the domain name * and the string following the slash should be a name within that domain. * * Names that do not contain a backslash are known as isolated names. * An isolated name may be a single label, such as john, or may be in * user principal name (UPN) form, such as john@example.com. * * domain\name * domain/name * name * name@domain * * If we encounter any of the forms above in arg, the @, / or \ separator * is replaced by \0 and the name and domain pointers are set to point to * the appropriate components in arg. Otherwise, name and domain pointers * will be set to NULL. */ void smb_name_parse(char *arg, char **account, char **domain) { char *p; *account = NULL; *domain = NULL; if ((p = strpbrk(arg, "/\\@")) != NULL) { if (*p == '@') { *p = '\0'; ++p; *domain = p; *account = arg; } else { *p = '\0'; ++p; *account = p; *domain = arg; } } } /* * The txid is an arbitrary transaction. A new txid is returned on each call. * * 0 or -1 are not assigned so that they can be used to detect * invalid conditions. */ uint32_t smb_get_txid(void) { static mutex_t txmutex; static uint32_t txid; uint32_t txid_ret; (void) mutex_lock(&txmutex); if (txid == 0) txid = time(NULL); do { ++txid; } while (txid == 0 || txid == (uint32_t)-1); txid_ret = txid; (void) mutex_unlock(&txmutex); return (txid_ret); }