1/* lock.c: The opielock() library function.
2
3%%% portions-copyright-cmetz-96
4Portions of this software are Copyright 1996-1999 by Craig Metz, All Rights
5Reserved. The Inner Net License Version 2 applies to these portions of
6the software.
7You should have received a copy of the license with this software. If
8you didn't get a copy, you may request one from <license@inner.net>.
9
10Portions of this software are Copyright 1995 by Randall Atkinson and Dan
11McDonald, All Rights Reserved. All Rights under this copyright are assigned
12to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
13License Agreement applies to this software.
14
15        History:
16
17	Modified by cmetz for OPIE 2.4. Use snprintf.
18	Modified by cmetz for OPIE 2.31. Put locks in a separate dir.
19            Bug fixes.
20	Modified by cmetz for OPIE 2.3. Do refcounts whether or not we
21            actually lock. Fixed USER_LOCKING=0 case.
22	Modified by cmetz for OPIE 2.22. Added reference count for locks.
23	    Changed lock filename/refcount symbol names to better indicate
24	    that they're not user serviceable.
25	Modified by cmetz for OPIE 2.2. Use FUNCTION declaration et al.
26            Use "principal" instead of "name" to make it clearer.
27            Ifdef around some headers, be more careful about allowed
28            error return values. Check open() return value properly.
29            Avoid NULL.
30        Created at NRL for OPIE 2.2 from opiesubr2.c
31
32$FreeBSD$
33*/
34#include "opie_cfg.h"
35#if HAVE_STRING_H
36#include <string.h>
37#endif /* HAVE_STRING_H */
38#if HAVE_UNISTD_H
39#include <unistd.h>
40#endif /* HAVE_UNISTD_H */
41#include <sys/stat.h>
42#include <syslog.h>
43#include <fcntl.h>
44#if HAVE_STDLIB_H
45#include <stdlib.h>
46#endif /* HAVE_STDLIB_H */
47#include <errno.h>
48#include "opie.h"
49
50#if !HAVE_LSTAT
51#define lstat(x, y) stat(x, y)
52#endif /* !HAVE_LSTAT */
53
54int __opie_lockrefcount = 0;
55static int do_atexit = 1;
56
57VOIDRET opiedisableaeh FUNCTION_NOARGS
58{
59  do_atexit = 0;
60}
61#if USER_LOCKING
62char *__opie_lockfilename = (char *)0;
63
64/* atexit() handler for opielock() */
65VOIDRET opieunlockaeh FUNCTION_NOARGS
66{
67  if (__opie_lockfilename) {
68    __opie_lockrefcount = 0;
69    opieunlock();
70  }
71}
72#endif /* USER_LOCKING */
73
74/*
75   Serialize (we hope) authentication of user to prevent race conditions.
76   Creates a lock file with a name of OPIE_LOCK_PREFIX with the user name
77   appended. This file contains the pid of the lock's owner and a time()
78   stamp. We use the former to check for dead owners and the latter to
79   provide an upper bound on the lock duration. If there are any problems,
80   we assume the lock is bogus.
81
82   The value of this locking and its security implications are still not
83   completely clear and require further study.
84
85   One could conceivably hack this facility to provide locking of user
86   accounts after several authentication failures.
87
88   Return -1 on low-level error, 0 if ok, 1 on locking failure.
89*/
90int opielock FUNCTION((principal), char *principal)
91{
92#if USER_LOCKING
93  int fh, waits = 0, rval = -1, pid, t, i;
94  char buffer[128], buffer2[128], *c, *c2;
95  struct stat statbuf[2];
96
97  if (getuid() && geteuid()) {
98#if DEBUG
99    syslog(LOG_DEBUG, "opielock: requires superuser priveleges");
100#endif /* DEBUG */
101    return -1;
102  };
103
104  if (__opie_lockfilename) {
105    __opie_lockrefcount++;
106    return 0;
107  }
108
109  if (!(__opie_lockfilename = (char *)malloc(sizeof(OPIE_LOCK_DIR) + 1 + strlen(principal))))
110    return -1;
111
112  strcpy(__opie_lockfilename, OPIE_LOCK_DIR);
113
114  if (mkdir(__opie_lockfilename, 0700) < 0)
115    if (errno != EEXIST)
116      return -1;
117
118  if (lstat(__opie_lockfilename, &statbuf[0]) < 0)
119    return -1;
120
121  if (statbuf[0].st_uid) {
122#if DEBUG
123    syslog(LOG_DEBUG, "opielock: %s isn't owned by the superuser.", __opie_lockfilename);
124#endif /* DEBUG */
125    return -1;
126  };
127
128  if (!S_ISDIR(statbuf[0].st_mode)) {
129#if DEBUG
130    syslog(LOG_DEBUG, "opielock: %s isn't a directory.", __opie_lockfilename);
131#endif /* DEBUG */
132    return -1;
133  };
134
135  if ((statbuf[0].st_mode & 0777) != 00700) {
136#if DEBUG
137    syslog(LOG_DEBUG, "opielock: permissions on %s are not correct.", __opie_lockfilename);
138#endif /* DEBUG */
139    return -1;
140  };
141
142  strcat(__opie_lockfilename, "/");
143  strcat(__opie_lockfilename, principal);
144
145  fh = -1;
146  while (fh < 0) {
147    if (!lstat(__opie_lockfilename, &statbuf[0]))
148      if (!S_ISREG(statbuf[0].st_mode))
149        goto lockret;
150
151    if ((fh = open(__opie_lockfilename, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0) {
152      if (lstat(__opie_lockfilename, &statbuf[1]) < 0)
153        goto lockret;
154      if (statbuf[0].st_ino != statbuf[1].st_ino)
155        goto lockret;
156      if (statbuf[0].st_mode != statbuf[1].st_mode)
157        goto lockret;
158      if ((fh = open(__opie_lockfilename, O_RDONLY, 0600)) < 0)
159        goto lockret;
160      if ((i = read(fh, buffer, sizeof(buffer))) <= 0)
161        goto lockret;
162
163      buffer[sizeof(buffer) - 1] = 0;
164      buffer[i - 1] = 0;
165
166      if (!(c = strchr(buffer, '\n')))
167        break;
168
169      *(c++) = 0;
170
171      if (!(c2 = strchr(c, '\n')))
172        break;
173
174      *(c2++) = 0;
175
176      if (!(pid = atoi(buffer)))
177        break;
178
179      if (!(t = atoi(c)))
180        break;
181
182      if ((t + OPIE_LOCK_TIMEOUT) < time(0))
183        break;
184
185      if (kill(pid, 0))
186        break;
187
188      close(fh);
189      fh = 0;
190      sleep(1);
191      if (waits++ > 3) {
192        rval = 1;
193        goto lockret;
194      };
195    };
196  };
197
198  if (lstat(__opie_lockfilename, &statbuf[0]) < 0)
199    goto lockret;
200  if (fstat(fh, &statbuf[1]) < 0)
201    goto lockret;
202  if (!S_ISREG(statbuf[0].st_mode) || (statbuf[0].st_mode != statbuf[1].st_mode) || (statbuf[0].st_ino != statbuf[1].st_ino))
203    goto lockret;
204
205  if (snprintf(buffer, sizeof(buffer), "%d\n%d\n", getpid(), time(0)) >= sizeof(buffer))
206    goto lockret;
207
208  i = strlen(buffer) + 1;
209  if (lseek(fh, 0, SEEK_SET)) {
210    close(fh);
211    unlink(__opie_lockfilename);
212    fh = 0;
213    goto lockret;
214  };
215  if (write(fh, buffer, i) != i) {
216    close(fh);
217    unlink(__opie_lockfilename);
218    fh = 0;
219    goto lockret;
220  };
221  close(fh);
222  if ((fh = open(__opie_lockfilename, O_RDWR, 0600)) < 0) {
223    unlink(__opie_lockfilename);
224    goto lockret;
225  };
226  if (read(fh, buffer2, i) != i) {
227    close(fh);
228    unlink(__opie_lockfilename);
229    fh = 0;
230    goto lockret;
231  };
232  close(fh);
233  if (memcmp(buffer, buffer2, i)) {
234    unlink(__opie_lockfilename);
235    goto lockret;
236  };
237
238  __opie_lockrefcount++;
239  rval = 0;
240  if (do_atexit)
241    atexit(opieunlockaeh);
242
243lockret:
244  if (fh >= 0)
245    close(fh);
246  if (!__opie_lockrefcount) {
247    free (__opie_lockfilename);
248    __opie_lockfilename = NULL;
249  };
250  return rval;
251#else /* USER_LOCKING */
252  __opie_lockrefcount++;
253  return 0;
254#endif /* USER_LOCKING */
255}
256