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 (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * This module provides the netbios and SMB negotiation, connect and 28 * disconnect interface. 29 */ 30 31 #include <unistd.h> 32 #include <syslog.h> 33 #include <synch.h> 34 #include <string.h> 35 #include <strings.h> 36 #include <pthread.h> 37 #include <errno.h> 38 #include <sys/types.h> 39 #include <sys/socket.h> 40 #include <netinet/in.h> 41 #include <arpa/inet.h> 42 #include <inttypes.h> 43 #include <netdb.h> 44 45 #include <smbsrv/libsmbrdr.h> 46 #include <smbsrv/netbios.h> 47 #include <smbrdr.h> 48 49 #define SMBRDR_DOMAIN_MAX 32 50 51 static uint16_t smbrdr_ports[] = { 52 IPPORT_SMB, 53 IPPORT_NETBIOS_SSN 54 }; 55 56 static int smbrdr_nports = sizeof (smbrdr_ports) / sizeof (smbrdr_ports[0]); 57 58 static struct sdb_session session_table[SMBRDR_DOMAIN_MAX]; 59 static mutex_t smbrdr_screate_mtx; 60 static uint32_t session_id = 0; 61 62 static struct sdb_session *smbrdr_session_init(char *, char *); 63 static int smbrdr_trnsprt_connect(struct sdb_session *, uint16_t); 64 static int smbrdr_session_connect(char *, char *); 65 static int smbrdr_smb_negotiate(struct sdb_session *); 66 static int smbrdr_smb_echo(struct sdb_session *); 67 static void smbrdr_session_disconnect(struct sdb_session *, int); 68 69 70 static void 71 smbrdr_session_clear(struct sdb_session *session) 72 { 73 bzero(session, sizeof (struct sdb_session) - sizeof (rwlock_t)); 74 } 75 76 /* 77 * Disconnects the session with given server. 78 */ 79 void 80 smbrdr_disconnect(const char *server) 81 { 82 struct sdb_session *session; 83 84 session = smbrdr_session_lock(server, SDB_SLCK_WRITE); 85 if (session) { 86 smbrdr_session_disconnect(session, 0); 87 smbrdr_session_unlock(session); 88 } 89 } 90 91 /* 92 * smbrdr_negotiate 93 * 94 * Negotiate a session with a domain controller in the specified domain. 95 * The domain must be one of values from the smbinfo that indicates the 96 * resource domain or the account domain. 97 * 98 * If a session already exists, we can use that one. Otherwise we create 99 * a new one. This sets up the session key and session security info that 100 * we'll need later to authenticate the user. The session security info 101 * is returned to support the SMB client pass-through authentication 102 * interface. 103 * 104 * Returns 0 on success, otherwise -1. 105 */ 106 /*ARGSUSED*/ 107 int 108 smbrdr_negotiate(char *domain_controller, char *domain) 109 { 110 struct sdb_session *session = 0; 111 int rc; 112 113 /* 114 * The mutex is to make session lookup and create atomic 115 * so we don't end up with two sessions with the same 116 * server. 117 */ 118 (void) mutex_lock(&smbrdr_screate_mtx); 119 session = smbrdr_session_lock(domain_controller, SDB_SLCK_WRITE); 120 if (session != 0) { 121 if (nb_keep_alive(session->sock, session->port) == 0) { 122 /* session is good, use it */ 123 smbrdr_session_unlock(session); 124 rc = 0; 125 goto done; 126 127 } else { 128 /* stale session */ 129 smbrdr_session_clear(session); 130 smbrdr_session_unlock(session); 131 } 132 } 133 134 rc = smbrdr_session_connect(domain_controller, domain); 135 done: 136 (void) mutex_unlock(&smbrdr_screate_mtx); 137 138 if (rc != 0) 139 syslog(LOG_DEBUG, "smbrdr_negotiate: cannot access domain"); 140 return (rc); 141 } 142 143 /* 144 * smbrdr_session_connect 145 * 146 * This is the entry point for establishing an SMB connection to a 147 * domain controller. A session structure is allocated, a netbios 148 * session is set up and the SMB protocol is negotiated. If this is 149 * successful, the returned session structure can be used to logon 150 * to the the domain. A null pointer is returned if the connect fails. 151 */ 152 static int 153 smbrdr_session_connect(char *domain_controller, char *domain) 154 { 155 struct sdb_session *session; 156 uint16_t port; 157 int rc = 0; 158 159 /* 160 * smbrdr_session_init() will lock the session so that it wouldn't 161 * be accessible until it's established otherwise another thread 162 * might get access to a session which is not fully established. 163 */ 164 if ((session = smbrdr_session_init(domain_controller, domain)) 165 == NULL) { 166 syslog(LOG_DEBUG, "smbrdr_session_init failed"); 167 return (-1); 168 } 169 170 for (port = 0; port < smbrdr_nports; ++port) { 171 rc = smbrdr_trnsprt_connect(session, smbrdr_ports[port]); 172 if (rc == 0) 173 break; 174 } 175 176 if (rc < 0) { 177 smbrdr_session_clear(session); 178 smbrdr_session_unlock(session); 179 syslog(LOG_DEBUG, "smbrdr: connect failed"); 180 return (-1); 181 } 182 183 if (smbrdr_smb_negotiate(session) < 0) { 184 (void) close(session->sock); 185 smbrdr_session_clear(session); 186 smbrdr_session_unlock(session); 187 syslog(LOG_DEBUG, "smbrdr: negotiate failed"); 188 return (-1); 189 } 190 191 smbrdr_session_unlock(session); 192 return (0); 193 } 194 195 196 /* 197 * smbrdr_trnsprt_connect 198 * 199 * Set up the TCP/IP and NETBIOS protocols for a session. This is just 200 * standard socket sutff. The paranoia check for socket descriptor 0 201 * is because we had a problem with this value and the console telnet 202 * interface will lock up if we use and/or close stdin (0). 203 * 204 * Return 0 on success. Otherwise return (-1) to indicate a problem. 205 */ 206 static int 207 smbrdr_trnsprt_connect(struct sdb_session *sess, uint16_t port) 208 { 209 char hostname[MAXHOSTNAMELEN]; 210 struct sockaddr_in sin; 211 struct sockaddr_in6 sin6; 212 int sock, rc; 213 smb_wchar_t unicode_server_name[SMB_PI_MAX_DOMAIN]; 214 char server_name[SMB_PI_MAX_DOMAIN]; 215 char ipstr[INET6_ADDRSTRLEN]; 216 217 if ((sock = socket(sess->srv_ipaddr.a_family, SOCK_STREAM, 0)) <= 0) { 218 syslog(LOG_DEBUG, "smbrdr: socket failed: %s", strerror(errno)); 219 return (-1); 220 } 221 if (sess->srv_ipaddr.a_family == AF_INET) { 222 bzero(&sin, sizeof (struct sockaddr_in)); 223 sin.sin_family = AF_INET; 224 sin.sin_addr.s_addr = sess->srv_ipaddr.a_ipv4; 225 sin.sin_port = htons(port); 226 rc = connect(sock, (struct sockaddr *)&sin, sizeof (sin)); 227 } else { 228 (void) smb_inet_ntop(&sess->srv_ipaddr, ipstr, 229 SMB_IPSTRLEN(sess->srv_ipaddr.a_family)); 230 bzero(&sin6, sizeof (struct sockaddr_in6)); 231 sin6.sin6_family = AF_INET6; 232 bcopy(&sess->srv_ipaddr.a_ipv6, &sin6.sin6_addr.s6_addr, 233 IPV6_ADDR_LEN); 234 sin6.sin6_port = htons(port); 235 rc = connect(sock, (struct sockaddr *)&sin6, sizeof (sin6)); 236 } 237 238 if (rc < 0) { 239 syslog(LOG_DEBUG, "smbrdr: connect failed: %s", 240 strerror(errno)); 241 if (sock != 0) 242 (void) close(sock); 243 return (-1); 244 } 245 246 (void) smb_mbstowcs(unicode_server_name, sess->srv_name, 247 SMB_PI_MAX_DOMAIN); 248 rc = ucstooem(server_name, unicode_server_name, SMB_PI_MAX_DOMAIN, 249 OEM_CPG_850); 250 if (rc == 0) { 251 syslog(LOG_DEBUG, "smbrdr: unicode conversion failed"); 252 if (sock != 0) 253 (void) close(sock); 254 return (-1); 255 } 256 257 /* 258 * If we are using NetBIOS, we need to set up a NETBIOS session. 259 * This typically implies that we will be using port 139. 260 * Otherwise, we're doing NetBIOS-less SMB, i.e. SMB over TCP, 261 * which is typically on port 445. 262 */ 263 if (port == IPPORT_NETBIOS_SSN) { 264 if (smb_getnetbiosname(hostname, MAXHOSTNAMELEN) != 0) { 265 syslog(LOG_DEBUG, "smbrdr: no hostname"); 266 if (sock != 0) 267 (void) close(sock); 268 return (-1); 269 } 270 271 rc = nb_session_request(sock, 272 server_name, sess->scope, hostname, sess->scope); 273 274 if (rc != 0) { 275 syslog(LOG_DEBUG, 276 "smbrdr: NBT session request to %s failed %d", 277 server_name, rc); 278 if (sock != 0) 279 (void) close(sock); 280 return (-1); 281 } 282 } 283 284 sess->sock = sock; 285 sess->port = port; 286 syslog(LOG_DEBUG, "smbrdr: connected on port %d", port); 287 sess->state = SDB_SSTATE_CONNECTED; 288 return (0); 289 } 290 291 /* 292 * smbrdr_smb_negotiate 293 * 294 * Negotiate the protocol we are going to use as described in CIFS 295 * section 4.1.1. The only protocol we support is NT LM 0.12, so we 296 * really expect to see dialect 0 in the response. The only other 297 * data gathered is the session key. 298 * 299 * Negotiate using ASCII strings. 300 * 301 * Return 0 on success. Otherwise return a -ve error code. 302 */ 303 static int 304 smbrdr_smb_negotiate(struct sdb_session *sess) 305 { 306 unsigned short dialect; 307 smbrdr_handle_t srh; 308 smb_hdr_t smb_hdr; 309 smb_msgbuf_t *mb; 310 DWORD status; 311 int rc; 312 uint8_t tmp_secmode; 313 uint8_t tmp_clen; 314 315 status = smbrdr_request_init(&srh, SMB_COM_NEGOTIATE, sess, 0, 0); 316 317 if (status != NT_STATUS_SUCCESS) 318 return (-1); 319 320 mb = &srh.srh_mbuf; 321 rc = smb_msgbuf_encode(mb, "bwbs", 0, 12, 0x02, "NT LM 0.12"); 322 if (rc <= 0) { 323 smbrdr_handle_free(&srh); 324 return (-1); 325 } 326 327 status = smbrdr_exchange(&srh, &smb_hdr, 0); 328 if (status != NT_STATUS_SUCCESS) { 329 syslog(LOG_DEBUG, "smbrdr: negotiate: %s", 330 xlate_nt_status(status)); 331 smbrdr_handle_free(&srh); 332 return (-1); 333 } 334 335 sess->secmode = 0; 336 sess->sesskey = 0; 337 sess->challenge_len = 0; 338 339 rc = smb_msgbuf_decode(mb, 340 "1.(dialect)w(mode)b12.(key)l(cap)l10.(keylen)b2.", 341 &dialect, &tmp_secmode, &sess->sesskey, &sess->remote_caps, 342 &tmp_clen); 343 344 if (rc <= 0 || dialect != 0) { 345 smbrdr_handle_free(&srh); 346 return (-1); 347 } 348 sess->secmode = tmp_secmode; 349 sess->challenge_len = tmp_clen; 350 351 rc = smb_msgbuf_decode(mb, "#c", 352 sess->challenge_len, sess->challenge_key); 353 if (rc <= 0) { 354 smbrdr_handle_free(&srh); 355 return (-1); 356 } 357 358 smbrdr_handle_free(&srh); 359 360 if ((sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) && 361 (sess->secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED)) { 362 sess->sign_ctx.ssc_flags |= SMB_SCF_REQUIRED; 363 syslog(LOG_DEBUG, "smbrdr: %s: signing required", 364 sess->srv_name); 365 } 366 367 sess->state = SDB_SSTATE_NEGOTIATED; 368 return (0); 369 } 370 371 /* 372 * smbrdr_session_init 373 * 374 * Allocate an available slot in session table for the specified domain 375 * information. 376 * 377 * IMPORTANT! the returned session will be locked caller has to unlock 378 * it by calling smbrdr_session_unlock() after it's done with 379 * the pointer. 380 */ 381 static struct sdb_session * 382 smbrdr_session_init(char *domain_controller, char *domain) 383 { 384 struct sdb_session *session = NULL; 385 smb_inaddr_t ipaddr; 386 int i, rc; 387 struct hostent *h; 388 389 if (domain_controller == NULL || domain == NULL) 390 return (NULL); 391 392 if ((h = smb_gethostbyname(domain_controller, &rc)) == NULL) { 393 syslog(LOG_DEBUG, "smbrdr: failed to resolve %s to IP (%d)", 394 domain_controller, rc); 395 return (NULL); 396 } 397 398 (void) memcpy(&ipaddr, h->h_addr, h->h_length); 399 ipaddr.a_family = h->h_addrtype; 400 freehostent(h); 401 402 for (i = 0; i < SMBRDR_DOMAIN_MAX; ++i) { 403 session = &session_table[i]; 404 405 (void) rw_wrlock(&session->rwl); 406 if (session->state == SDB_SSTATE_START) { 407 smbrdr_session_clear(session); 408 (void) strlcpy(session->srv_name, domain_controller, 409 MAXHOSTNAMELEN); 410 (void) smb_strupr(session->srv_name); 411 412 session->srv_ipaddr = ipaddr; 413 (void) strlcpy(session->domain, domain, MAXHOSTNAMELEN); 414 (void) smb_strupr(session->domain); 415 416 (void) smb_config_getstr(SMB_CI_NBSCOPE, session->scope, 417 sizeof (session->scope)); 418 419 (void) strlcpy(session->native_os, 420 "Solaris", SMB_PI_MAX_NATIVE_OS); 421 (void) strlcpy(session->native_lanman, 422 "Windows NT 4.0", SMB_PI_MAX_LANMAN); 423 session->sock = -1; 424 session->port = smbrdr_ports[0]; 425 session->smb_flags = SMB_FLAGS_CANONICALIZED_PATHS 426 | SMB_FLAGS_CASE_INSENSITIVE; 427 428 session->smb_flags2 = SMB_FLAGS2_KNOWS_LONG_NAMES 429 | SMB_FLAGS2_KNOWS_EAS; 430 431 /* 432 * Note that by sending vc=0 server will shutdown all 433 * the other connections with NAS if there is any. 434 */ 435 session->vc = 0; 436 session->sid = ++session_id; 437 if (session->sid == 0) 438 session->sid = 1; 439 session->state = SDB_SSTATE_INIT; 440 return (session); 441 } 442 (void) rw_unlock(&session->rwl); 443 } 444 445 syslog(LOG_DEBUG, "smbrdr: no session available"); 446 return (NULL); 447 } 448 449 /* 450 * smbrdr_session_disconnect 451 * 452 * This is the entry point for disconnecting an SMB connection. Ensure 453 * that all logons and shares associated with this session are 454 * terminated and then free the session. 455 * 456 * if 'cleanup' is 1 it means that only sessions that are not active 457 * should be cleaned up. if 'cleanup' is 0 disconnect the session in any 458 * states. 459 */ 460 static void 461 smbrdr_session_disconnect(struct sdb_session *session, int cleanup) 462 { 463 int state; 464 465 if (session == NULL) 466 return; 467 468 state = session->state; 469 if ((state != SDB_SSTATE_DISCONNECTING) && 470 (state != SDB_SSTATE_CLEANING) && 471 (state != SDB_SSTATE_START)) { 472 if ((cleanup == 0) || (state == SDB_SSTATE_STALE)) { 473 /* 474 * if session is in stale state it means the connection 475 * is lost so no logoff, tdcon, or close can actually 476 * be sent, thus only cleanup our side. 477 */ 478 session->state = (state == SDB_SSTATE_STALE) 479 ? SDB_SSTATE_CLEANING : SDB_SSTATE_DISCONNECTING; 480 (void) smbrdr_logoffx(&session->logon); 481 nb_close(session->sock); 482 smbrdr_session_clear(session); 483 } 484 } 485 } 486 487 /* 488 * smbrdr_session_unlock 489 * 490 * Unlock given session structure. 491 */ 492 void 493 smbrdr_session_unlock(struct sdb_session *session) 494 { 495 if (session) 496 (void) rw_unlock(&session->rwl); 497 } 498 499 /* 500 * smbrdr_session_lock 501 * 502 * Lookup the session associated with the specified domain controller. 503 * If a match is found, we return a pointer to the session, Otherwise 504 * we return null. Only sessions in "negotiated" state are checked. 505 * This mechanism is very simple and implies that we 506 * should only ever have one session open to any domain controller. 507 * 508 * IMPORTANT! the returned session will be locked caller has to unlock 509 * it by calling smbrdr_session_unlock() after it's done with 510 * the pointer. 511 */ 512 struct sdb_session * 513 smbrdr_session_lock(const char *server, int lmode) 514 { 515 struct sdb_session *session; 516 int i; 517 518 if (server == NULL) 519 return (NULL); 520 521 for (i = 0; i < SMBRDR_DOMAIN_MAX; ++i) { 522 session = &session_table[i]; 523 524 (lmode == SDB_SLCK_READ) ? (void) rw_rdlock(&session->rwl) : 525 (void) rw_wrlock(&session->rwl); 526 527 if (session->state == SDB_SSTATE_STALE) { 528 smbrdr_session_clear(session); 529 (void) rw_unlock(&session->rwl); 530 continue; 531 } 532 533 if ((session->state == SDB_SSTATE_NEGOTIATED) && 534 (smb_strcasecmp(session->srv_name, server, 0) == 0)) { 535 return (session); 536 } 537 538 (void) rw_unlock(&session->rwl); 539 } 540 541 return (NULL); 542 } 543 544 /* 545 * smbrdr_dump_sessions 546 * 547 * Debug function to dump the session table. 548 */ 549 void 550 smbrdr_dump_sessions(void) 551 { 552 struct sdb_session *session; 553 struct sdb_logon *logon; 554 char ipstr[INET6_ADDRSTRLEN]; 555 int i; 556 557 for (i = 0; i < SMBRDR_DOMAIN_MAX; ++i) { 558 session = &session_table[i]; 559 560 (void) rw_rdlock(&session->rwl); 561 if (session->state != SDB_SSTATE_START) { 562 (void) smb_inet_ntop(&session->srv_ipaddr, ipstr, 563 SMB_IPSTRLEN(session->srv_ipaddr.a_family)); 564 syslog(LOG_DEBUG, "session[%d]: state=%d", 565 i, session->state); 566 syslog(LOG_DEBUG, "session[%d]: %s %s (%s)", i, 567 session->domain, session->srv_name, ipstr); 568 syslog(LOG_DEBUG, "session[%d]: %s %s (sock=%d)", i, 569 session->native_os, session->native_lanman, 570 session->sock); 571 572 logon = &session->logon; 573 if (logon->type != SDB_LOGON_NONE) 574 syslog(LOG_DEBUG, "logon[%d]: %s (uid=%d)", 575 i, logon->username, logon->uid); 576 } 577 (void) rw_unlock(&session->rwl); 578 } 579 } 580 581 int 582 smbrdr_echo(const char *server) 583 { 584 struct sdb_session *session; 585 int res = 0; 586 587 if ((session = smbrdr_session_lock(server, SDB_SLCK_WRITE)) != NULL) { 588 res = smbrdr_smb_echo(session); 589 smbrdr_session_unlock(session); 590 } 591 592 return (res); 593 } 594 595 /* 596 * This request can be used to test the connection to the server. The 597 * server should echo the data sent. The server should ignore the tid 598 * in the header, so this request when there are no tree connections. 599 * See CIFS/1.0 section 4.1.7. 600 * 601 * Return 0 on success. Otherwise return a -ve error code. 602 */ 603 static int 604 smbrdr_smb_echo(struct sdb_session *session) 605 { 606 static char *echo_str = "smbrdr"; 607 smbrdr_handle_t srh; 608 smb_hdr_t smb_hdr; 609 DWORD status; 610 int rc; 611 612 if ((session->state == SDB_SSTATE_DISCONNECTING) || 613 (session->state == SDB_SSTATE_CLEANING) || 614 (session->state == SDB_SSTATE_STALE)) { 615 return (-1); 616 } 617 618 status = smbrdr_request_init(&srh, SMB_COM_ECHO, session, 0, 0); 619 if (status != NT_STATUS_SUCCESS) 620 return (-1); 621 622 rc = smb_msgbuf_encode(&srh.srh_mbuf, "bwws", 1, 1, 623 strlen(echo_str), echo_str); 624 if (rc <= 0) { 625 smbrdr_handle_free(&srh); 626 return (-1); 627 } 628 629 status = smbrdr_exchange(&srh, &smb_hdr, 10); 630 smbrdr_handle_free(&srh); 631 632 if (status != NT_STATUS_SUCCESS) 633 return (-1); 634 635 return (0); 636 } 637