1/*
2   Unix SMB/Netbios implementation.
3   Version 1.9.
4   connection claim routines
5   Copyright (C) Andrew Tridgell 1998
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 2 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20*/
21
22#include "includes.h"
23
24
25extern fstring remote_machine;
26
27extern int DEBUGLEVEL;
28
29#ifdef WITH_UTMP
30static void utmp_yield(pid_t pid, const connection_struct *conn, int i);
31static void utmp_claim(const struct connect_record *crec, connection_struct *conn, int i);
32#endif
33
34/****************************************************************************
35simple routines to do connection counting
36****************************************************************************/
37BOOL yield_connection(connection_struct *conn,char *name,int max_connections)
38{
39	struct connect_record crec;
40	pstring fname;
41	int fd;
42	pid_t mypid = getpid();
43	int i;
44
45	DEBUG(3,("Yielding connection to %s\n",name));
46
47	if (max_connections <= 0)
48		return(True);
49
50	memset((char *)&crec,'\0',sizeof(crec));
51
52	pstrcpy(fname,lp_lockdir());
53	trim_string(fname,"","/");
54
55	pstrcat(fname,"/");
56	pstrcat(fname,name);
57	pstrcat(fname,".LCK");
58
59	fd = sys_open(fname,O_RDWR,0);
60	if (fd == -1) {
61		DEBUG(2,("Couldn't open lock file %s (%s)\n",fname,strerror(errno)));
62		return(False);
63	}
64
65	if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_WRLCK)==False) {
66		DEBUG(0,("ERROR: can't get lock on %s\n", fname));
67		return False;
68	}
69
70	/* find the right spot */
71	for (i=0;i<max_connections;i++) {
72		if (read(fd, &crec,sizeof(crec)) != sizeof(crec)) {
73			DEBUG(2,("Entry not found in lock file %s\n",fname));
74			if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
75				DEBUG(0,("ERROR: can't release lock on %s\n", fname));
76			}
77			close(fd);
78			return(False);
79		}
80		if (crec.pid == mypid && crec.cnum == conn->cnum)
81			break;
82	}
83
84	if (crec.pid != mypid || crec.cnum != conn->cnum) {
85		if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
86			DEBUG(0,("ERROR: can't release lock on %s\n", fname));
87		}
88		close(fd);
89		DEBUG(2,("Entry not found in lock file %s\n",fname));
90		return(False);
91	}
92
93	memset((void *)&crec,'\0',sizeof(crec));
94
95	/* remove our mark */
96	if (sys_lseek(fd,i*sizeof(crec),SEEK_SET) != i*sizeof(crec) ||
97	    write(fd, &crec,sizeof(crec)) != sizeof(crec)) {
98		DEBUG(2,("Couldn't update lock file %s (%s)\n",fname,strerror(errno)));
99		if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
100			DEBUG(0,("ERROR: can't release lock on %s\n", fname));
101		}
102		close(fd);
103		return(False);
104	}
105
106	if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
107		DEBUG(0,("ERROR: can't release lock on %s\n", fname));
108	}
109
110	DEBUG(3,("Yield successful\n"));
111
112	close(fd);
113
114#ifdef WITH_UTMP
115	utmp_yield(mypid, conn, i);
116#endif
117
118	return(True);
119}
120
121
122/****************************************************************************
123simple routines to do connection counting
124****************************************************************************/
125BOOL claim_connection(connection_struct *conn,char *name,int max_connections,BOOL Clear)
126{
127	extern int Client;
128	struct connect_record crec;
129	pstring fname;
130	int fd=-1;
131	int i,foundi= -1;
132	int total_recs;
133
134	if (max_connections <= 0)
135		return(True);
136
137	DEBUG(5,("trying claim %s %s %d\n",lp_lockdir(),name,max_connections));
138
139	pstrcpy(fname,lp_lockdir());
140	trim_string(fname,"","/");
141
142	if (!directory_exist(fname,NULL))
143		mkdir(fname,0755);
144
145	pstrcat(fname,"/");
146	pstrcat(fname,name);
147	pstrcat(fname,".LCK");
148
149	if (!file_exist(fname,NULL)) {
150		fd = sys_open(fname,O_RDWR|O_CREAT|O_EXCL, 0644);
151	}
152
153	if (fd == -1) {
154		fd = sys_open(fname,O_RDWR,0);
155	}
156
157	if (fd == -1) {
158		DEBUG(1,("couldn't open lock file %s\n",fname));
159		return(False);
160	}
161
162	if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_WRLCK)==False) {
163		DEBUG(0,("ERROR: can't get lock on %s\n", fname));
164		return False;
165	}
166
167	total_recs = get_file_size(fname) / sizeof(crec);
168
169	/* find a free spot */
170	for (i=0;i<max_connections;i++) {
171		if (i>=total_recs ||
172		    sys_lseek(fd,i*sizeof(crec),SEEK_SET) != i*sizeof(crec) ||
173		    read(fd,&crec,sizeof(crec)) != sizeof(crec)) {
174			if (foundi < 0) foundi = i;
175			break;
176		}
177
178		if (Clear && crec.pid && !process_exists(crec.pid)) {
179			if(sys_lseek(fd,i*sizeof(crec),SEEK_SET) != i*sizeof(crec)) {
180              DEBUG(0,("claim_connection: ERROR: sys_lseek failed to seek \
181to %d\n", (int)(i*sizeof(crec)) ));
182              continue;
183            }
184			memset((void *)&crec,'\0',sizeof(crec));
185			write(fd, &crec,sizeof(crec));
186			if (foundi < 0) foundi = i;
187			continue;
188		}
189		if (foundi < 0 && (!crec.pid || !process_exists(crec.pid))) {
190			foundi=i;
191			if (!Clear) break;
192		}
193	}
194
195	if (foundi < 0) {
196		DEBUG(3,("no free locks in %s\n",fname));
197		if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
198			DEBUG(0,("ERROR: can't release lock on %s\n", fname));
199		}
200		close(fd);
201		return(False);
202	}
203
204	/* fill in the crec */
205	memset((void *)&crec,'\0',sizeof(crec));
206	crec.magic = 0x280267;
207	crec.pid = getpid();
208	if (conn) {
209		crec.cnum = conn->cnum;
210		crec.uid = conn->uid;
211		crec.gid = conn->gid;
212		StrnCpy(crec.name,
213			lp_servicename(SNUM(conn)),sizeof(crec.name)-1);
214	} else {
215		crec.cnum = -1;
216	}
217	crec.start = time(NULL);
218
219	StrnCpy(crec.machine,remote_machine,sizeof(crec.machine)-1);
220	StrnCpy(crec.addr,client_addr(Client),sizeof(crec.addr)-1);
221
222	/* make our mark */
223	if (sys_lseek(fd,foundi*sizeof(crec),SEEK_SET) != foundi*sizeof(crec) ||
224	    write(fd, &crec,sizeof(crec)) != sizeof(crec)) {
225		if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
226			DEBUG(0,("ERROR: can't release lock on %s\n", fname));
227		}
228		close(fd);
229		return(False);
230	}
231
232	if (fcntl_lock(fd,SMB_F_SETLKW,0,1,F_UNLCK)==False) {
233		DEBUG(0,("ERROR: can't release lock on %s\n", fname));
234	}
235
236	close(fd);
237
238#ifdef WITH_UTMP
239	utmp_claim(&crec, conn, foundi);
240#endif
241
242	return(True);
243}
244
245#ifdef WITH_UTMP
246
247/****************************************************************************
248Reflect connection status in utmp/wtmp files.
249	T.D.Lee@durham.ac.uk  September 1999
250
251Hints for porting:
252	o Always attempt to use programmatic interface (pututline() etc.)
253	o The "x" (utmpx/wtmpx; HAVE_UTMPX_H) seems preferable.
254
255OS status:
256	Solaris 2.x:  Tested on 2.6 and 2.7; should be OK on other flavours.
257		T.D.Lee@durham.ac.uk
258	HPUX 9.x:  Not tested.  Appears not to have "x".
259	IRIX 6.5:  Not tested.  Appears to have "x".
260
261OS observations:
262	Almost every OS seems to have its own quirks.
263
264Notes:
265	The 4 byte 'ut_id' component is vital to distinguish connections,
266	of which there could be several hundered or even thousand.
267	Entries seem to be printable characters, with optional NULL pads.
268
269	We need to be distinct from other entries in utmp/wtmp.
270
271	Observed things: therefore avoid them.  Add to this list please.
272	From Solaris 2.x (because that's what I have):
273		'sN'	: run-levels; N: [0-9]
274		'co'	: console
275		'CC'	: arbitrary things;  C: [a-z]
276		'rXNN'	: rlogin;  N: [0-9]; X: [0-9a-z]
277		'tXNN'	: rlogin;  N: [0-9]; X: [0-9a-z]
278		'/NNN'	: Solaris CDE
279		'ftpZ'	: ftp (Z is the number 255, aka 0377, aka 0xff)
280	Mostly a record uses the same 'ut_id' in both "utmp" and "wtmp",
281	but differences have been seen.
282
283	Arbitrarily I have chosen to use a distinctive 'SM' for the
284	first two bytes.
285
286	The remaining two encode the connection number used in samba locking
287	functions "claim_connection() and "yield_connection()".  This seems
288	to be a "nicely behaved" number: starting from 0 then working up
289	looking for an available slot.
290
291****************************************************************************/
292
293#include <utmp.h>
294
295/*
296 * Apparently AIX has utmpx.h but doesn't implement it.
297 * The test for this ought to be (a) more automatic (b) elsewhere.
298 */
299#if defined (AIX)
300#undef HAVE_UTMPX_H
301#endif
302
303#ifdef HAVE_UTMPX_H
304#include <utmpx.h>
305#endif
306
307static const char *ut_id_encstr =
308	"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
309
310static
311int
312ut_id_encode(int i, char *fourbyte)
313{
314	int nbase;
315
316	fourbyte[0] = 'S';
317	fourbyte[1] = 'M';
318
319/*
320 * Encode remaining 2 bytes from 'i'.
321 * 'ut_id_encstr' is the character set on which modulo arithmetic is done.
322 * Example: digits would produce the base-10 numbers from '001'.
323 */
324	nbase = strlen(ut_id_encstr);
325
326	fourbyte[3] = ut_id_encstr[i % nbase];
327	i /= nbase;
328	fourbyte[2] = ut_id_encstr[i % nbase];
329	i /= nbase;
330
331	return(i);	/* 0: good; else overflow */
332}
333
334/*
335 * Fill in a utmp (not utmpx) template
336 */
337static int utmp_fill(struct utmp *u, const connection_struct *conn, pid_t pid,
338  int i, pstring host)
339{
340#if defined(HAVE_UT_UT_TIME)
341	struct timeval timeval;
342#endif /* defined(HAVE_UT_UT_TIME) */
343	int rc = 0;
344
345#if defined(HAVE_UT_UT_USER)
346	pstrcpy(u->ut_user, conn->user);
347#endif /* defined(HAVE_UT_UT_USER) */
348
349#if defined(HAVE_UT_UT_NAME)
350	pstrcpy(u->ut_name, conn->user);
351#endif /* defined(HAVE_UT_UT_NAME) */
352
353	slprintf(u->ut_line, 12, "smb/%d", i);
354
355	u->ut_pid = pid;
356
357#if defined(HAVE_UT_UT_TIME)
358	gettimeofday(&timeval, NULL);
359	u->ut_time = timeval.tv_sec;
360#endif /* defined(HAVE_UT_UT_TIME) */
361
362#if defined(HAVE_UT_UT_TV)
363	gettimeofday(&timeval, NULL);
364	u->ut_tv = timeval;
365#endif /* defined(HAVE_UT_UT_TV) */
366
367#if defined(HAVE_UT_UT_HOST)
368	if (host) {
369		pstrcpy(u->ut_host, host);
370	}
371#endif /* defined(HAVE_UT_UT_HOST) */
372
373#if defined(HAVE_UT_UT_ID)
374	rc = ut_id_encode(i, u->ut_id);
375#endif /* defined(HAVE_UT_UT_ID) */
376
377	return(rc);
378}
379
380/*
381 * Default paths to various {u,w}tmp{,x} files
382 */
383#ifdef	HAVE_UTMPX_H
384
385static char *ux_pathname =
386# if defined (UTMPX_FILE)
387	UTMPX_FILE ;
388# elif defined (_UTMPX_FILE)
389	_UTMPX_FILE ;
390# elif defined (_PATH_UTMPX)
391	_PATH_UTMPX ;
392# else
393	"" ;
394# endif
395
396static char *wx_pathname =
397# if defined (WTMPX_FILE)
398	WTMPX_FILE ;
399# elif defined (_WTMPX_FILE)
400	_WTMPX_FILE ;
401# elif defined (_PATH_WTMPX)
402	_PATH_WTMPX ;
403# else
404	"" ;
405# endif
406
407#endif	/* HAVE_UTMPX_H */
408
409static char *ut_pathname =
410# if defined (UTMP_FILE)
411	UTMP_FILE ;
412# elif defined (_UTMP_FILE)
413	_UTMP_FILE ;
414# elif defined (_PATH_UTMP)
415	_PATH_UTMP ;
416# else
417	"" ;
418# endif
419
420static char *wt_pathname =
421# if defined (WTMP_FILE)
422	WTMP_FILE ;
423# elif defined (_WTMP_FILE)
424	_WTMP_FILE ;
425# elif defined (_PATH_WTMP)
426	_PATH_WTMP ;
427# else
428	"" ;
429# endif
430
431/*
432 * Get name of {u,w}tmp{,x} file.
433 *	return: fname contains filename
434 *		Possibly empty if this code not yet ported to this system.
435 *
436 * utmp{,x}:  try "utmp dir", then default (a define)
437 * wtmp{,x}:  try "wtmp dir", then "utmp dir", then default (a define)
438 */
439static void uw_pathname(pstring fname, const char *uw_name, const char *uw_default)
440{
441	pstring dirname;
442
443	pstrcpy(dirname, "");
444
445	/* For w-files, first look for explicit "wtmp dir" */
446	if (uw_name[0] == 'w') {
447		pstrcpy(dirname,lp_wtmpdir());
448		trim_string(dirname,"","/");
449	}
450
451	/* For u-files and non-explicit w-dir, look for "utmp dir" */
452	if (dirname == 0 || strlen(dirname) == 0) {
453		pstrcpy(dirname,lp_utmpdir());
454		trim_string(dirname,"","/");
455	}
456
457	/* If explicit directory above, use it */
458	if (dirname != 0 && strlen(dirname) != 0) {
459		pstrcpy(fname, dirname);
460		pstrcat(fname, "/");
461		pstrcat(fname, uw_name);
462		return;
463	}
464
465	/* No explicit directory: attempt to use default paths */
466	if (strlen(uw_default) == 0) {
467		/* No explicit setting, no known default.
468		 * Has it yet been ported to this OS?
469		 */
470		DEBUG(2,("uw_pathname: unable to determine pathname\n"));
471	}
472	pstrcpy(fname, uw_default);
473}
474
475static void utmp_update(const struct utmp *u, pstring host)
476{
477	pstring fname;
478
479#ifdef HAVE_UTMPX_H
480	struct utmpx ux, *uxrc;
481
482	getutmpx(u, &ux);
483	if (host) {
484#if defined(HAVE_UX_UT_SYSLEN)
485		ux.ut_syslen = strlen(host);
486#endif /* defined(HAVE_UX_UT_SYSLEN) */
487		pstrcpy(ux.ut_host, host);
488	}
489
490	uw_pathname(fname, "utmpx", ux_pathname);
491	DEBUG(2,("utmp_update: fname:%s\n", fname));
492	if (strlen(fname) != 0) {
493		utmpxname(fname);
494	}
495	setutxent();
496	uxrc = pututxline(&ux);
497	endutxent();
498	if (uxrc == NULL) {
499		DEBUG(2,("utmp_update: pututxline() failed\n"));
500		return;
501	}
502
503	uw_pathname(fname, "wtmpx", wx_pathname);
504	DEBUG(2,("utmp_update: fname:%s\n", fname));
505	if (strlen(fname) != 0) {
506		updwtmpx(fname, &ux);
507	}
508#else
509	uw_pathname(fname, "utmp", ut_pathname);
510	DEBUG(2,("utmp_update: fname:%s\n", fname));
511	if (strlen(fname) != 0) {
512		utmpname(fname);
513	}
514	setutent();
515	pututline(u);
516	endutent();
517
518	uw_pathname(fname, "wtmp", wt_pathname);
519
520	/* *** Hmmm.  Appending wtmp (as distinct from overwriting utmp) has
521	me baffled.  How is it to be done? *** */
522#endif
523}
524
525static void utmp_yield(pid_t pid, const connection_struct *conn, int i)
526{
527	struct utmp u;
528	int nopen;
529
530	if (! lp_utmp(SNUM(conn))) {
531		DEBUG(2,("utmp_yield: lp_utmp() NULL\n"));
532		return;
533	}
534
535	nopen = conn_num_open();
536	DEBUG(2,("utmp_yield: conn: user:%s cnum:%d i:%d (nopen:%d)\n",
537	  conn->user, conn->cnum, i, nopen));
538
539	if (lp_utmp_consolidate() && nopen > 1) {
540		DEBUG(2,("utmp_yield: utmp consolidate: %d entries open\n", nopen));
541		return;
542	}
543
544	memset((char *)&u, '\0', sizeof(struct utmp));
545	u.ut_type = DEAD_PROCESS;
546	u.ut_exit.e_termination = 0;
547	u.ut_exit.e_exit = 0;
548	if (utmp_fill(&u, conn, pid, i, NULL) == 0) {
549		utmp_update(&u, NULL);
550	}
551}
552
553static void utmp_claim(const struct connect_record *crec, connection_struct *conn, int i)
554{
555	struct utmp u;
556	pstring host;
557	int nopen;
558
559	if (conn == NULL) {
560		DEBUG(2,("utmp_claim: conn NULL\n"));
561		return;
562	}
563
564	if (! lp_utmp(SNUM(conn))) {
565		DEBUG(2,("utmp_claim: lp_utmp() NULL\n"));
566		return;
567	}
568
569	nopen = conn_num_open();
570	if (lp_utmp_consolidate() && nopen > 1) {
571		DEBUG(2,("utmp_claim: utmp consolidate: %d entries open\n", nopen));
572		return;
573	}
574
575	pstrcpy(host, lp_utmp_hostname());
576	if (host == 0 || strlen(host) == 0) {
577		pstrcpy(host, crec->machine);
578	}
579	else {
580		/* explicit "utmp host": expand for any "%" variables */
581		standard_sub(conn, host);
582	}
583
584	nopen = conn_num_open();
585	DEBUG(2,("utmp_claim: conn: user:%s cnum:%d i:%d (nopen:%d)\n",
586	  conn->user, conn->cnum, i, nopen));
587	DEBUG(2,("utmp_claim: crec: pid:%d, cnum:%d name:%s addr:%s mach:%s host:%s\n",
588	  crec->pid, crec->cnum, crec->name, crec->addr, crec->machine, host));
589
590
591	memset((char *)&u, '\0', sizeof(struct utmp));
592	u.ut_type = USER_PROCESS;
593	if (utmp_fill(&u, conn, crec->pid, i, host) == 0) {
594		utmp_update(&u, host);
595	}
596}
597
598#endif
599