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 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 /*
27 * This module handles the primary domain controller location protocol.
28 * The document claims to be version 1.15 of the browsing protocol. It also
29 * claims to specify the mailslot protocol.
30 *
31 * The NETLOGON protocol uses \MAILSLOT\NET mailslots. The protocol
32 * specification is incomplete, contains errors and is out-of-date but
33 * it does provide some useful background information. The document
34 * doesn't mention the NETLOGON_SAMLOGON version of the protocol.
35 */
36
37 #include <stdlib.h>
38 #include <syslog.h>
39 #include <alloca.h>
40 #include <arpa/inet.h>
41 #include <resolv.h>
42
43 #include <smbsrv/mailslot.h>
44 #include <smbsrv/libsmbns.h>
45 #include <smbns_browser.h>
46 #include <smbns_netbios.h>
47
48 static void smb_netlogon_query(struct name_entry *server, char *mailbox,
49 char *domain);
50
51 static void smb_netlogon_samlogon(struct name_entry *, char *,
52 char *, smb_sid_t *);
53
54 static void smb_netlogon_send(struct name_entry *name, char *domain,
55 unsigned char *buffer, int count);
56
57 static void smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr);
58 static int smb_better_dc(uint32_t cur_ip, uint32_t new_ip);
59
60 /*
61 * ntdomain_info
62 * Temporary. It should be removed once NBTD is integrated.
63 */
64 extern smb_ntdomain_t ntdomain_info;
65 extern mutex_t ntdomain_mtx;
66 extern cond_t ntdomain_cv;
67
68 /*
69 * smb_netlogon_request
70 *
71 * This is the entry point locating the resource domain PDC. A netlogon
72 * request is sent using the specified protocol on the specified network.
73 * Note that we need to know the domain SID in order to use the samlogon
74 * format.
75 *
76 * Netlogon responses are received asynchronously and eventually handled
77 * in smb_netlogon_receive.
78 */
79 void
smb_netlogon_request(struct name_entry * server,char * domain)80 smb_netlogon_request(struct name_entry *server, char *domain)
81 {
82 smb_domain_t di;
83 smb_sid_t *sid = NULL;
84 int protocol = NETLOGON_PROTO_NETLOGON;
85
86 if (domain == NULL || *domain == '\0')
87 return;
88
89 (void) mutex_lock(&ntdomain_mtx);
90 (void) strlcpy(ntdomain_info.n_domain, domain,
91 sizeof (ntdomain_info.n_domain));
92 (void) mutex_unlock(&ntdomain_mtx);
93
94 smb_config_getdomaininfo(di.di_nbname, NULL, di.di_sid, NULL, NULL);
95 if (smb_strcasecmp(di.di_nbname, domain, 0) == 0) {
96 if ((sid = smb_sid_fromstr(di.di_sid)) != NULL)
97 protocol = NETLOGON_PROTO_SAMLOGON;
98 }
99
100 if (protocol == NETLOGON_PROTO_SAMLOGON)
101 smb_netlogon_samlogon(server, MAILSLOT_NETLOGON_SAMLOGON_RDC,
102 domain, sid);
103 else
104 smb_netlogon_query(server, MAILSLOT_NETLOGON_RDC, domain);
105
106 smb_sid_free(sid);
107 }
108
109 /*
110 * smb_netlogon_receive
111 *
112 * This is where we handle all incoming NetLogon messages. Currently, we
113 * ignore requests from anyone else. We are only interested in responses
114 * to our own requests. The NetLogonResponse provides the name of the PDC.
115 * If we don't already have a controller name, we use the name provided
116 * in the message. Otherwise we use the name already in the environment.
117 */
118 void
smb_netlogon_receive(struct datagram * datagram,char * mailbox,unsigned char * data,int datalen)119 smb_netlogon_receive(struct datagram *datagram,
120 char *mailbox,
121 unsigned char *data,
122 int datalen)
123 {
124 struct netlogon_opt {
125 char *mailslot;
126 void (*handler)();
127 } netlogon_opt[] = {
128 { MAILSLOT_NETLOGON_RDC, smb_netlogon_rdc_rsp },
129 { MAILSLOT_NETLOGON_SAMLOGON_RDC, smb_netlogon_rdc_rsp },
130 };
131
132 smb_msgbuf_t mb;
133 unsigned short opcode;
134 char src_name[SMB_PI_MAX_HOST];
135 smb_wchar_t unicode_src_name[SMB_PI_MAX_HOST];
136 uint32_t src_ipaddr;
137 char *junk;
138 char *primary;
139 char *domain;
140 int i;
141 char ipstr[16];
142 int rc;
143
144 src_ipaddr = datagram->src.addr_list.sin.sin_addr.s_addr;
145
146 /*
147 * The datagram->src.name is in oem codepage format.
148 * Therefore, we need to convert it to unicode and
149 * store it in multi-bytes format.
150 */
151 (void) oemtoucs(unicode_src_name, (char *)datagram->src.name,
152 SMB_PI_MAX_HOST, OEM_CPG_850);
153 (void) smb_wcstombs(src_name, unicode_src_name, SMB_PI_MAX_HOST);
154
155 (void) trim_whitespace(src_name);
156
157 (void) inet_ntop(AF_INET, (const void *)(&src_ipaddr), ipstr,
158 sizeof (ipstr));
159 syslog(LOG_DEBUG, "NetLogonReceive: src=%s [%s], mbx=%s",
160 src_name, ipstr, mailbox);
161
162 smb_msgbuf_init(&mb, data, datalen, 0);
163
164 if (smb_msgbuf_decode(&mb, "w", &opcode) < 0) {
165 syslog(LOG_ERR, "NetLogonReceive: decode error");
166 smb_msgbuf_term(&mb);
167 return;
168 }
169
170 switch (opcode) {
171 case LOGON_PRIMARY_RESPONSE:
172 /*
173 * Message contains:
174 * PDC name (MBS), PDC name (Unicode), Domain name (unicode)
175 */
176 rc = smb_msgbuf_decode(&mb, "sUU", &junk, &primary, &domain);
177 if (rc < 0) {
178 syslog(LOG_ERR,
179 "NetLogonResponse: opcode %d decode error",
180 opcode);
181 smb_msgbuf_term(&mb);
182 return;
183 }
184 break;
185
186 case LOGON_SAM_LOGON_RESPONSE:
187 case LOGON_SAM_USER_UNKNOWN:
188 /*
189 * Message contains:
190 * PDC name, User name, Domain name (all unicode)
191 */
192 rc = smb_msgbuf_decode(&mb, "UUU", &primary, &junk, &domain);
193 if (rc < 0) {
194 syslog(LOG_ERR,
195 "NetLogonResponse: opcode %d decode error",
196 opcode);
197 smb_msgbuf_term(&mb);
198 return;
199 }
200
201 /*
202 * skip past the "\\" prefix
203 */
204 primary += strspn(primary, "\\");
205 break;
206
207 default:
208 /*
209 * We don't respond to PDC discovery requests.
210 */
211 syslog(LOG_DEBUG, "NetLogonReceive: opcode 0x%04x", opcode);
212 smb_msgbuf_term(&mb);
213 return;
214 }
215
216 if (domain == NULL || primary == NULL) {
217 syslog(LOG_ERR, "NetLogonResponse: malformed packet");
218 smb_msgbuf_term(&mb);
219 return;
220 }
221
222 syslog(LOG_DEBUG, "DC Offer Domain=%s PDC=%s From=%s",
223 domain, primary, src_name);
224
225 (void) mutex_lock(&ntdomain_mtx);
226 if (strcasecmp(domain, ntdomain_info.n_domain)) {
227 syslog(LOG_DEBUG, "NetLogonResponse: other domain "
228 "%s, requested %s", domain, ntdomain_info.n_domain);
229 smb_msgbuf_term(&mb);
230 (void) mutex_unlock(&ntdomain_mtx);
231 return;
232 }
233 (void) mutex_unlock(&ntdomain_mtx);
234
235 for (i = 0; i < sizeof (netlogon_opt)/sizeof (netlogon_opt[0]); ++i) {
236 if (strcasecmp(netlogon_opt[i].mailslot, mailbox) == 0) {
237 syslog(LOG_DEBUG, "NetLogonReceive: %s", mailbox);
238 (*netlogon_opt[i].handler)(primary, src_ipaddr);
239 smb_msgbuf_term(&mb);
240 return;
241 }
242 }
243
244 syslog(LOG_DEBUG, "NetLogonReceive[%s]: unknown mailslot", mailbox);
245 smb_msgbuf_term(&mb);
246 }
247
248
249
250 /*
251 * smb_netlogon_query
252 *
253 * Build and send a LOGON_PRIMARY_QUERY to the MAILSLOT_NETLOGON. At some
254 * point we should receive a LOGON_PRIMARY_RESPONSE in the mailslot we
255 * specify in the request.
256 *
257 * struct NETLOGON_QUERY {
258 * unsigned short Opcode; # LOGON_PRIMARY_QUERY
259 * char ComputerName[]; # ASCII hostname. The response
260 * # is sent to <ComputerName>(00).
261 * char MailslotName[]; # MAILSLOT_NETLOGON
262 * char Pad[]; # Pad to short
263 * wchar_t ComputerName[] # UNICODE hostname
264 * DWORD NT_Version; # 0x00000001
265 * WORD LmNTToken; # 0xffff
266 * WORD Lm20Token; # 0xffff
267 * };
268 */
269 static void
smb_netlogon_query(struct name_entry * server,char * mailbox,char * domain)270 smb_netlogon_query(struct name_entry *server,
271 char *mailbox,
272 char *domain)
273 {
274 smb_msgbuf_t mb;
275 int offset, announce_len, data_length, name_lengths;
276 unsigned char buffer[MAX_DATAGRAM_LENGTH];
277 char hostname[NETBIOS_NAME_SZ];
278
279 if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0)
280 return;
281
282 name_lengths = strlen(mailbox)+1+strlen(hostname)+1;
283
284 /*
285 * The (name_lengths & 1) part is to word align the name_lengths
286 * before the wc equiv strlen and the "+ 2" is to cover the two
287 * zero bytes that terminate the wchar string.
288 */
289 data_length = sizeof (short) + name_lengths + (name_lengths & 1) +
290 smb_wcequiv_strlen(hostname) + 2 + sizeof (long) + sizeof (short) +
291 sizeof (short);
292
293 offset = smb_browser_load_transact_header(buffer,
294 sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
295 MAILSLOT_NETLOGON);
296
297 if (offset < 0)
298 return;
299
300 smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
301
302 announce_len = smb_msgbuf_encode(&mb, "wssUlww",
303 (short)LOGON_PRIMARY_QUERY,
304 hostname,
305 mailbox,
306 hostname,
307 0x1,
308 0xffff,
309 0xffff);
310
311 if (announce_len <= 0) {
312 smb_msgbuf_term(&mb);
313 syslog(LOG_ERR, "NetLogonQuery: encode error");
314 return;
315 }
316
317 smb_netlogon_send(server, domain, buffer, offset + announce_len);
318 smb_msgbuf_term(&mb);
319 }
320
321
322 /*
323 * smb_netlogon_samlogon
324 *
325 * The SamLogon version of the NetLogon request uses the workstation trust
326 * account and, I think, may be a prerequisite to the challenge/response
327 * netr authentication. The trust account username is the hostname with a
328 * $ appended. The mailslot for this request is MAILSLOT_NTLOGON. At some
329 * we should receive a LOGON_SAM_LOGON_RESPONSE in the mailslot we
330 * specify in the request.
331 *
332 * struct NETLOGON_SAM_LOGON {
333 * unsigned short Opcode; # LOGON_SAM_LOGON_REQUEST
334 * unsigned short RequestCount; # 0
335 * wchar_t UnicodeComputerName; # hostname
336 * wchar_t UnicodeUserName; # hostname$
337 * char *MailslotName; # response mailslot
338 * DWORD AllowableAccountControlBits; # 0x80 = WorkstationTrustAccount
339 * DWORD DomainSidSize; # domain sid length in bytes
340 * BYTE *DomainSid; # domain sid
341 * uint32_t NT_Version; # 0x00000001
342 * unsigned short LmNTToken; # 0xffff
343 * unsigned short Lm20Token; # 0xffff
344 * };
345 */
346 static void
smb_netlogon_samlogon(struct name_entry * server,char * mailbox,char * domain,smb_sid_t * domain_sid)347 smb_netlogon_samlogon(struct name_entry *server,
348 char *mailbox,
349 char *domain,
350 smb_sid_t *domain_sid)
351 {
352 smb_msgbuf_t mb;
353 unsigned domain_sid_len;
354 char *username;
355 unsigned char buffer[MAX_DATAGRAM_LENGTH];
356 int offset;
357 int announce_len;
358 int data_length;
359 int name_length;
360 char hostname[NETBIOS_NAME_SZ];
361
362 syslog(LOG_DEBUG, "NetLogonSamLogonReq: %s", domain);
363
364 if (smb_getnetbiosname(hostname, sizeof (hostname)) != 0)
365 return;
366
367 /*
368 * The username will be the trust account name on the PDC.
369 */
370 name_length = strlen(hostname) + 2;
371 username = alloca(name_length);
372 (void) snprintf(username, name_length, "%s$", hostname);
373
374 domain_sid_len = smb_sid_len(domain_sid);
375 /*
376 * Add 2 to wide-char equivalent strlen to cover the
377 * two zero bytes that terminate the wchar string.
378 */
379 name_length = strlen(mailbox)+1;
380
381 data_length = sizeof (short)
382 + sizeof (short)
383 + smb_wcequiv_strlen(hostname) + 2
384 + smb_wcequiv_strlen(username) + 2
385 + name_length
386 + sizeof (long)
387 + sizeof (long)
388 + domain_sid_len + 3 /* padding */
389 + sizeof (long)
390 + sizeof (short)
391 + sizeof (short);
392
393 offset = smb_browser_load_transact_header(buffer,
394 sizeof (buffer), data_length, ONE_WAY_TRANSACTION,
395 MAILSLOT_NTLOGON);
396
397 if (offset < 0) {
398 syslog(LOG_ERR, "NetLogonSamLogonReq: header error");
399 return;
400 }
401
402 /*
403 * The domain SID is padded with 3 leading zeros.
404 */
405 smb_msgbuf_init(&mb, buffer + offset, sizeof (buffer) - offset, 0);
406 announce_len = smb_msgbuf_encode(&mb, "wwUUsll3.#clww",
407 (short)LOGON_SAM_LOGON_REQUEST,
408 0, /* RequestCount */
409 hostname, /* UnicodeComputerName */
410 username, /* UnicodeUserName */
411 mailbox, /* MailslotName */
412 0x00000080, /* AllowableAccountControlBits */
413 domain_sid_len, /* DomainSidSize */
414 domain_sid_len, domain_sid, /* DomainSid */
415 0x00000001, /* NT_Version */
416 0xffff, /* LmNTToken */
417 0xffff); /* Lm20Token */
418
419 if (announce_len <= 0) {
420 syslog(LOG_ERR, "NetLogonSamLogonReq: encode error");
421 smb_msgbuf_term(&mb);
422 return;
423 }
424
425 smb_netlogon_send(server, domain, buffer, offset + announce_len);
426 smb_msgbuf_term(&mb);
427 }
428
429
430 /*
431 * Send a query for each version of the protocol.
432 */
433 static void
smb_netlogon_send(struct name_entry * name,char * domain,unsigned char * buffer,int count)434 smb_netlogon_send(struct name_entry *name,
435 char *domain,
436 unsigned char *buffer,
437 int count)
438 {
439 static char suffix[] = { 0x1B, 0x1C };
440 struct name_entry dname;
441 struct name_entry *dest;
442 struct name_entry *dest_dup;
443 int i;
444
445 for (i = 0; i < sizeof (suffix)/sizeof (suffix[0]); i++) {
446 smb_init_name_struct((unsigned char *)domain, suffix[i],
447 0, 0, 0, 0, 0, &dname);
448
449 syslog(LOG_DEBUG, "SmbNetlogonSend");
450 smb_netbios_name_logf(&dname);
451 if ((dest = smb_name_find_name(&dname)) != 0) {
452 dest_dup = smb_netbios_name_dup(dest, 1);
453 smb_name_unlock_name(dest);
454 if (dest_dup) {
455 (void) smb_netbios_datagram_send(name,
456 dest_dup, buffer, count);
457 free(dest_dup);
458 }
459 } else {
460 syslog(LOG_DEBUG,
461 "SmbNetlogonSend: could not find %s<0x%X>",
462 domain, suffix[i]);
463 }
464 }
465 }
466
467 /*
468 * smb_netlogon_rdc_rsp
469 *
470 * This is where we process netlogon responses for the resource domain.
471 * The src_name is the real name of the remote machine.
472 */
473 static void
smb_netlogon_rdc_rsp(char * src_name,uint32_t src_ipaddr)474 smb_netlogon_rdc_rsp(char *src_name, uint32_t src_ipaddr)
475 {
476 static int initialized = 0;
477 uint32_t ipaddr;
478 uint32_t prefer_ipaddr;
479 char ipstr[INET_ADDRSTRLEN];
480 char srcip[INET_ADDRSTRLEN];
481 int rc;
482
483 (void) inet_ntop(AF_INET, &src_ipaddr, srcip, INET_ADDRSTRLEN);
484
485 rc = smb_config_getstr(SMB_CI_DOMAIN_SRV, ipstr, INET_ADDRSTRLEN);
486 if (rc == SMBD_SMF_OK) {
487 rc = inet_pton(AF_INET, ipstr, &prefer_ipaddr);
488 if (rc == 0)
489 prefer_ipaddr = 0;
490
491 if (!initialized) {
492 syslog(LOG_DEBUG, "SMB DC Preference: %s", ipstr);
493 initialized = 1;
494 }
495 }
496
497 (void) mutex_lock(&ntdomain_mtx);
498 syslog(LOG_DEBUG, "DC Offer [%s]: %s [%s]",
499 ntdomain_info.n_domain, src_name, srcip);
500
501 if (ntdomain_info.n_ipaddr != 0) {
502 if (prefer_ipaddr != 0 &&
503 prefer_ipaddr == ntdomain_info.n_ipaddr) {
504 syslog(LOG_DEBUG, "DC for %s: %s [%s]",
505 ntdomain_info.n_domain, src_name, srcip);
506 (void) mutex_unlock(&ntdomain_mtx);
507 return;
508 }
509
510 ipaddr = ntdomain_info.n_ipaddr;
511 } else
512 ipaddr = 0;
513
514 if (smb_better_dc(ipaddr, src_ipaddr) ||
515 (prefer_ipaddr != 0 && prefer_ipaddr == src_ipaddr)) {
516 /* set nbtd cache */
517 (void) strlcpy(ntdomain_info.n_name, src_name,
518 SMB_PI_MAX_DOMAIN);
519 ntdomain_info.n_ipaddr = src_ipaddr;
520 (void) cond_broadcast(&ntdomain_cv);
521 syslog(LOG_DEBUG, "DC discovered for %s: %s [%s]",
522 ntdomain_info.n_domain, src_name, srcip);
523 }
524 (void) mutex_unlock(&ntdomain_mtx);
525 }
526
527 static int
smb_better_dc(uint32_t cur_ip,uint32_t new_ip)528 smb_better_dc(uint32_t cur_ip, uint32_t new_ip)
529 {
530 smb_inaddr_t ipaddr;
531
532 /*
533 * If we don't have any current DC,
534 * then use the new one of course.
535 */
536
537 if (cur_ip == 0)
538 return (1);
539 /*
540 * see if there is a DC in the
541 * same subnet
542 */
543
544 ipaddr.a_family = AF_INET;
545 ipaddr.a_ipv4 = cur_ip;
546 if (smb_nic_is_same_subnet(&ipaddr))
547 return (0);
548
549 ipaddr.a_family = AF_INET;
550 ipaddr.a_ipv4 = new_ip;
551 if (smb_nic_is_same_subnet(&ipaddr))
552 return (1);
553 /*
554 * Otherwise, just keep the old one.
555 */
556 return (0);
557 }
558