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/*
23 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
25 */
26
27/*
28 * Server Service (srvsvc) client side RPC library interface. The
29 * srvsvc interface allows a client to query a server for information
30 * on shares, sessions, connections and files on the server. Some
31 * functions are available via anonymous IPC while others require
32 * administrator privilege. Also, some functions return NT status
33 * values while others return Win32 errors codes.
34 */
35
36#include <sys/errno.h>
37#include <sys/tzfile.h>
38#include <stdio.h>
39#include <time.h>
40#include <strings.h>
41#include <unistd.h>
42
43#include <smbsrv/libsmb.h>
44#include <smbsrv/libmlsvc.h>
45#include <smbsrv/smbinfo.h>
46#include <smbsrv/ndl/srvsvc.ndl>
47
48/*
49 * Information level for NetShareGetInfo.
50 */
51DWORD srvsvc_info_level = 1;
52
53/*
54 * Bind to the the SRVSVC.
55 *
56 * If username argument is NULL, an anonymous connection will be established.
57 * Otherwise, an authenticated connection will be established.
58 */
59static int
60srvsvc_open(char *server, char *domain, char *username, mlsvc_handle_t *handle)
61{
62	smb_domainex_t di;
63
64	if (server == NULL || domain == NULL) {
65		if (!smb_domain_getinfo(&di))
66			return (-1);
67
68		server = di.d_dci.dc_name;
69		domain = di.d_primary.di_nbname;
70	}
71
72	if (username == NULL)
73		username = MLSVC_ANON_USER;
74
75	if (ndr_rpc_bind(handle, server, domain, username, "SRVSVC") != 0)
76		return (-1);
77
78	return (0);
79}
80
81/*
82 * Unbind the SRVSVC connection.
83 */
84static void
85srvsvc_close(mlsvc_handle_t *handle)
86{
87	ndr_rpc_unbind(handle);
88}
89
90/*
91 * This is a client side routine for NetShareGetInfo.
92 * Levels 0 and 1 work with an anonymous connection but
93 * level 2 requires administrator access.
94 */
95int
96srvsvc_net_share_get_info(char *server, char *domain, char *netname)
97{
98	struct mlsm_NetShareGetInfo arg;
99	mlsvc_handle_t handle;
100	int rc;
101	int opnum;
102	struct mslm_NetShareInfo_0 *info0;
103	struct mslm_NetShareInfo_1 *info1;
104	struct mslm_NetShareInfo_2 *info2;
105	int len;
106	char user[SMB_USERNAME_MAXLEN];
107
108	if (netname == NULL)
109		return (-1);
110
111	if (srvsvc_info_level == 2)
112		smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
113
114	if (srvsvc_open(server, domain, user, &handle) != 0)
115		return (-1);
116
117	opnum = SRVSVC_OPNUM_NetShareGetInfo;
118	bzero(&arg, sizeof (struct mlsm_NetShareGetInfo));
119
120	len = strlen(server) + 4;
121	arg.servername = ndr_rpc_malloc(&handle, len);
122	if (arg.servername == NULL) {
123		srvsvc_close(&handle);
124		return (-1);
125	}
126
127	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
128	arg.netname = (LPTSTR)netname;
129	arg.level = srvsvc_info_level; /* share information level */
130
131	rc = ndr_rpc_call(&handle, opnum, &arg);
132	if ((rc != 0) || (arg.status != 0)) {
133		srvsvc_close(&handle);
134		return (-1);
135	}
136
137	switch (arg.result.switch_value) {
138	case 0:
139		info0 = arg.result.ru.info0;
140		smb_tracef("srvsvc shi0_netname=%s", info0->shi0_netname);
141		break;
142
143	case 1:
144		info1 = arg.result.ru.info1;
145		smb_tracef("srvsvc shi1_netname=%s", info1->shi1_netname);
146		smb_tracef("srvsvc shi1_type=%u", info1->shi1_type);
147
148		if (info1->shi1_comment)
149			smb_tracef("srvsvc shi1_comment=%s",
150			    info1->shi1_comment);
151		break;
152
153	case 2:
154		info2 = arg.result.ru.info2;
155		smb_tracef("srvsvc shi2_netname=%s", info2->shi2_netname);
156		smb_tracef("srvsvc shi2_type=%u", info2->shi2_type);
157
158		if (info2->shi2_comment)
159			smb_tracef("srvsvc shi2_comment=%s",
160			    info2->shi2_comment);
161
162		smb_tracef("srvsvc shi2_perms=%d", info2->shi2_permissions);
163		smb_tracef("srvsvc shi2_max_use=%d", info2->shi2_max_uses);
164		smb_tracef("srvsvc shi2_cur_use=%d", info2->shi2_current_uses);
165
166		if (info2->shi2_path)
167			smb_tracef("srvsvc shi2_path=%s", info2->shi2_path);
168
169		if (info2->shi2_passwd)
170			smb_tracef("srvsvc shi2_passwd=%s", info2->shi2_passwd);
171		break;
172
173	default:
174		smb_tracef("srvsvc: unknown level");
175		break;
176	}
177
178	srvsvc_close(&handle);
179	return (0);
180}
181
182/*
183 * This is a client side routine for NetSessionEnum.
184 * NetSessionEnum requires administrator rights.
185 */
186int
187srvsvc_net_session_enum(char *server, char *domain, char *netname)
188{
189	struct mslm_NetSessionEnum arg;
190	mlsvc_handle_t handle;
191	int rc;
192	int opnum;
193	struct mslm_infonres infonres;
194	struct mslm_SESSION_INFO_1 *nsi1;
195	int len;
196	char user[SMB_USERNAME_MAXLEN];
197
198	if (netname == NULL)
199		return (-1);
200
201	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
202
203	rc = srvsvc_open(server, domain, user, &handle);
204	if (rc != 0)
205		return (-1);
206
207	opnum = SRVSVC_OPNUM_NetSessionEnum;
208	bzero(&arg, sizeof (struct mslm_NetSessionEnum));
209
210	len = strlen(server) + 4;
211	arg.servername = ndr_rpc_malloc(&handle, len);
212	if (arg.servername == NULL) {
213		srvsvc_close(&handle);
214		return (-1);
215	}
216
217	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
218	infonres.entriesread = 0;
219	infonres.entries = 0;
220	arg.level = 1;
221	arg.result.level = 1;
222	arg.result.bufptr.p = &infonres;
223	arg.resume_handle = 0;
224	arg.pref_max_len = 0xFFFFFFFF;
225
226	rc = ndr_rpc_call(&handle, opnum, &arg);
227	if ((rc != 0) || (arg.status != 0)) {
228		srvsvc_close(&handle);
229		return (-1);
230	}
231
232	/* Only the first session info is dereferenced. */
233	nsi1 = ((struct mslm_infonres *)arg.result.bufptr.p)->entries;
234
235	smb_tracef("srvsvc switch_value=%d", arg.level);
236	smb_tracef("srvsvc sesi1_cname=%s", nsi1->sesi1_cname);
237	smb_tracef("srvsvc sesi1_uname=%s", nsi1->sesi1_uname);
238	smb_tracef("srvsvc sesi1_nopens=%u", nsi1->sesi1_nopens);
239	smb_tracef("srvsvc sesi1_time=%u", nsi1->sesi1_time);
240	smb_tracef("srvsvc sesi1_itime=%u", nsi1->sesi1_itime);
241	smb_tracef("srvsvc sesi1_uflags=%u", nsi1->sesi1_uflags);
242
243	srvsvc_close(&handle);
244	return (0);
245}
246
247/*
248 * This is a client side routine for NetConnectEnum.
249 * NetConnectEnum requires administrator rights.
250 * Level 0 and level 1 requests are supported.
251 */
252int
253srvsvc_net_connect_enum(char *server, char *domain, char *netname, int level)
254{
255	struct mslm_NetConnectEnum arg;
256	mlsvc_handle_t handle;
257	int rc;
258	int opnum;
259	struct mslm_NetConnectInfo1 info1;
260	struct mslm_NetConnectInfo0 info0;
261	struct mslm_NetConnectInfoBuf1 *cib1;
262	int len;
263	char user[SMB_USERNAME_MAXLEN];
264
265	if (netname == NULL)
266		return (-1);
267
268	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
269
270	rc = srvsvc_open(server, domain, user, &handle);
271	if (rc != 0)
272		return (-1);
273
274	opnum = SRVSVC_OPNUM_NetConnectEnum;
275	bzero(&arg, sizeof (struct mslm_NetConnectEnum));
276
277	len = strlen(server) + 4;
278	arg.servername = ndr_rpc_malloc(&handle, len);
279	if (arg.servername == NULL) {
280		srvsvc_close(&handle);
281		return (-1);
282	}
283
284	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
285	arg.qualifier = (LPTSTR)netname;
286
287	switch (level) {
288	case 0:
289		arg.info.level = 0;
290		arg.info.switch_value = 0;
291		arg.info.ru.info0 = &info0;
292		info0.entries_read = 0;
293		info0.ci0 = 0;
294		break;
295	case 1:
296		arg.info.level = 1;
297		arg.info.switch_value = 1;
298		arg.info.ru.info1 = &info1;
299		info1.entries_read = 0;
300		info1.ci1 = 0;
301		break;
302	default:
303		srvsvc_close(&handle);
304		return (-1);
305	}
306
307	arg.resume_handle = 0;
308	arg.pref_max_len = 0xFFFFFFFF;
309
310	rc = ndr_rpc_call(&handle, opnum, &arg);
311	if ((rc != 0) || (arg.status != 0)) {
312		srvsvc_close(&handle);
313		return (-1);
314	}
315
316	smb_tracef("srvsvc switch_value=%d", arg.info.switch_value);
317
318	switch (level) {
319	case 0:
320		if (arg.info.ru.info0 && arg.info.ru.info0->ci0) {
321			smb_tracef("srvsvc coni0_id=%x",
322			    arg.info.ru.info0->ci0->coni0_id);
323		}
324		break;
325	case 1:
326		if (arg.info.ru.info1 && arg.info.ru.info1->ci1) {
327			cib1 = arg.info.ru.info1->ci1;
328
329			smb_tracef("srvsvc coni_uname=%s",
330			    cib1->coni1_username ?
331			    (char *)cib1->coni1_username : "(null)");
332			smb_tracef("srvsvc coni1_netname=%s",
333			    cib1->coni1_netname ?
334			    (char *)cib1->coni1_netname : "(null)");
335			smb_tracef("srvsvc coni1_nopens=%u",
336			    cib1->coni1_num_opens);
337			smb_tracef("srvsvc coni1_time=%u", cib1->coni1_time);
338			smb_tracef("srvsvc coni1_num_users=%u",
339			    cib1->coni1_num_users);
340		}
341		break;
342
343	default:
344		smb_tracef("srvsvc: unknown level");
345		break;
346	}
347
348	srvsvc_close(&handle);
349	return (0);
350}
351
352/*
353 * Compare the time here with the remote time on the server
354 * and report clock skew.
355 */
356void
357srvsvc_timecheck(char *server, char *domain)
358{
359	char			hostname[MAXHOSTNAMELEN];
360	struct timeval		dc_tv;
361	struct tm		dc_tm;
362	struct tm		*tm;
363	time_t			tnow;
364	time_t			tdiff;
365	int			priority;
366
367	if (srvsvc_net_remote_tod(server, domain, &dc_tv, &dc_tm) < 0) {
368		syslog(LOG_DEBUG, "srvsvc_net_remote_tod failed");
369		return;
370	}
371
372	tnow = time(NULL);
373
374	if (tnow > dc_tv.tv_sec)
375		tdiff = (tnow - dc_tv.tv_sec) / SECSPERMIN;
376	else
377		tdiff = (dc_tv.tv_sec - tnow) / SECSPERMIN;
378
379	if (tdiff != 0) {
380		(void) strlcpy(hostname, "localhost", MAXHOSTNAMELEN);
381		(void) gethostname(hostname, MAXHOSTNAMELEN);
382
383		priority = (tdiff > 2) ? LOG_NOTICE : LOG_DEBUG;
384		syslog(priority, "DC [%s] clock skew detected: %u minutes",
385		    server, tdiff);
386
387		tm = gmtime(&dc_tv.tv_sec);
388		syslog(priority, "%-8s  UTC: %s", server, asctime(tm));
389		tm = gmtime(&tnow);
390		syslog(priority, "%-8s  UTC: %s", hostname, asctime(tm));
391	}
392}
393
394/*
395 * Synchronize the local system clock with the domain controller.
396 */
397void
398srvsvc_timesync(void)
399{
400	smb_domainex_t di;
401	struct timeval tv;
402	struct tm tm;
403	time_t tsecs;
404
405	if (!smb_domain_getinfo(&di))
406		return;
407
408	if (srvsvc_net_remote_tod(di.d_dci.dc_name, di.d_primary.di_nbname,
409	    &tv, &tm) != 0)
410		return;
411
412	if (settimeofday(&tv, 0))
413		smb_tracef("unable to set system time");
414
415	tsecs = time(0);
416	(void) localtime_r(&tsecs, &tm);
417	smb_tracef("SrvsvcTimeSync %s", ctime((time_t *)&tv.tv_sec));
418}
419
420/*
421 * NetRemoteTOD to get the current GMT time from a Windows NT server.
422 */
423int
424srvsvc_gettime(unsigned long *t)
425{
426	smb_domainex_t di;
427	struct timeval tv;
428	struct tm tm;
429
430	if (!smb_domain_getinfo(&di))
431		return (-1);
432
433	if (srvsvc_net_remote_tod(di.d_dci.dc_name, di.d_primary.di_nbname,
434	    &tv, &tm) != 0)
435		return (-1);
436
437	*t = tv.tv_sec;
438	return (0);
439}
440
441/*
442 * This is a client side routine for NetRemoteTOD, which gets the time
443 * and date from a remote system. The time information is returned in
444 * the timeval and tm.
445 *
446 * typedef struct _TIME_OF_DAY_INFO {
447 *	DWORD tod_elapsedt;  // seconds since 00:00:00 January 1 1970 GMT
448 *	DWORD tod_msecs;     // arbitrary milliseconds (since reset)
449 *	DWORD tod_hours;     // current hour [0-23]
450 *	DWORD tod_mins;      // current minute [0-59]
451 *	DWORD tod_secs;      // current second [0-59]
452 *	DWORD tod_hunds;     // current hundredth (0.01) second [0-99]
453 *	LONG tod_timezone;   // time zone of the server
454 *	DWORD tod_tinterval; // clock tick time interval
455 *	DWORD tod_day;       // day of the month [1-31]
456 *	DWORD tod_month;     // month of the year [1-12]
457 *	DWORD tod_year;      // current year
458 *	DWORD tod_weekday;   // day of the week since sunday [0-6]
459 * } TIME_OF_DAY_INFO;
460 *
461 * The time zone of the server is calculated in minutes from Greenwich
462 * Mean Time (GMT). For time zones west of Greenwich, the value is
463 * positive; for time zones east of Greenwich, the value is negative.
464 * A value of -1 indicates that the time zone is undefined.
465 *
466 * The clock tick value represents a resolution of one ten-thousandth
467 * (0.0001) second.
468 */
469int
470srvsvc_net_remote_tod(char *server, char *domain, struct timeval *tv,
471    struct tm *tm)
472{
473	struct mslm_NetRemoteTOD	arg;
474	struct mslm_TIME_OF_DAY_INFO	*tod;
475	mlsvc_handle_t			handle;
476	int				rc;
477	int				opnum;
478	int				len;
479	char				user[SMB_USERNAME_MAXLEN];
480
481	smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);
482
483	rc = srvsvc_open(server, domain, user, &handle);
484	if (rc != 0)
485		return (-1);
486
487	opnum = SRVSVC_OPNUM_NetRemoteTOD;
488	bzero(&arg, sizeof (struct mslm_NetRemoteTOD));
489
490	len = strlen(server) + 4;
491	arg.servername = ndr_rpc_malloc(&handle, len);
492	if (arg.servername == NULL) {
493		srvsvc_close(&handle);
494		return (-1);
495	}
496
497	(void) snprintf((char *)arg.servername, len, "\\\\%s", server);
498
499	rc = ndr_rpc_call(&handle, opnum, &arg);
500	if ((rc != 0) || (arg.status != 0)) {
501		srvsvc_close(&handle);
502		return (-1);
503	}
504
505	/*
506	 * We're assigning milliseconds to microseconds
507	 * here but the value's not really relevant.
508	 */
509	tod = arg.bufptr;
510
511	if (tv) {
512		tv->tv_sec = tod->tod_elapsedt;
513		tv->tv_usec = tod->tod_msecs;
514	}
515
516	if (tm) {
517		tm->tm_sec = tod->tod_secs;
518		tm->tm_min = tod->tod_mins;
519		tm->tm_hour = tod->tod_hours;
520		tm->tm_mday = tod->tod_day;
521		tm->tm_mon = tod->tod_month - 1;
522		tm->tm_year = tod->tod_year - 1900;
523		tm->tm_wday = tod->tod_weekday;
524	}
525
526	srvsvc_close(&handle);
527	return (0);
528}
529