]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nfsdcltrack/nfsdcltrack.c
1d262a211cc77f0d86e2b5af688d51be670211a6
[nfs-utils.git] / utils / nfsdcltrack / nfsdcltrack.c
1 /*
2  * nfsdcltrack.c -- NFSv4 client name tracking program
3  *
4  * Copyright (C) 2012 Jeff Layton <jlayton@redhat.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif /* HAVE_CONFIG_H */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdbool.h>
31 #include <getopt.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <libgen.h>
38 #include <sys/inotify.h>
39 #ifdef HAVE_SYS_CAPABILITY_H
40 #include <sys/prctl.h>
41 #include <sys/capability.h>
42 #endif
43
44 #include "xlog.h"
45 #include "sqlite.h"
46
47 #ifndef CLD_DEFAULT_STORAGEDIR
48 #define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
49 #endif
50
51 /* defined by RFC 3530 */
52 #define NFS4_OPAQUE_LIMIT       1024
53
54 /* private data structures */
55 struct cltrack_cmd {
56         char *name;
57         bool needs_arg;
58         int (*func)(const char *arg);
59 };
60
61 /* forward declarations */
62 static int cltrack_init(const char *unused);
63 static int cltrack_create(const char *id);
64 static int cltrack_remove(const char *id);
65 static int cltrack_check(const char *id);
66 static int cltrack_gracedone(const char *gracetime);
67
68 /* global variables */
69 static struct option longopts[] =
70 {
71         { "help", 0, NULL, 'h' },
72         { "debug", 0, NULL, 'd' },
73         { "foreground", 0, NULL, 'f' },
74         { "storagedir", 1, NULL, 's' },
75         { NULL, 0, 0, 0 },
76 };
77
78 static struct cltrack_cmd commands[] =
79 {
80         { "init", false, cltrack_init },
81         { "create", true, cltrack_create },
82         { "remove", true, cltrack_remove },
83         { "check", true, cltrack_check },
84         { "gracedone", true, cltrack_gracedone },
85         { NULL, false, NULL },
86 };
87
88 static char *storagedir = CLD_DEFAULT_STORAGEDIR;
89
90 /* common buffer for holding id4 blobs */
91 static unsigned char blob[NFS4_OPAQUE_LIMIT];
92
93 static void
94 usage(char *progname)
95 {
96         printf("%s [ -hfd ] [ -s dir ] < cmd > < arg >\n", progname);
97         printf("Where < cmd > is one of the following and takes the following < arg >:\n");
98         printf("    init\n");
99         printf("    create <nfs_client_id4>\n");
100         printf("    remove <nfs_client_id4>\n");
101         printf("    check  <nfs_client_id4>\n");
102         printf("    gracedone <epoch time>\n");
103 }
104
105
106 /**
107  * hex_to_bin - convert a hex digit to its real value
108  * @ch: ascii character represents hex digit
109  *
110  * hex_to_bin() converts one hex digit to its actual value or -1 in case of bad
111  * input.
112  *
113  * Note: borrowed from lib/hexdump.c in the Linux kernel sources.
114  */
115 static int
116 hex_to_bin(char ch)
117 {
118         if ((ch >= '0') && (ch <= '9'))
119                 return ch - '0';
120         ch = tolower(ch);
121         if ((ch >= 'a') && (ch <= 'f'))
122                 return ch - 'a' + 10;
123         return -1;
124 }
125
126 /**
127  * hex_str_to_bin - convert a hexidecimal string into a binary blob
128  *
129  * @src: string of hex digit pairs
130  * @dst: destination buffer to hold binary data
131  * @dstsize: size of the destination buffer
132  *
133  * Walk a string of hex digit pairs and convert them into binary data. Returns
134  * the resulting length of the binary data or a negative error code. If the
135  * data will not fit in the buffer, it returns -ENOBUFS (but will likely have
136  * clobbered the dst buffer in the process of determining that). If there are
137  * non-hexidecimal characters in the src, or an odd number of them then it
138  * returns -EINVAL.
139  */
140 static ssize_t
141 hex_str_to_bin(const char *src, unsigned char *dst, ssize_t dstsize)
142 {
143         unsigned char *tmpdst = dst;
144
145         while (*src) {
146                 int hi, lo;
147
148                 /* make sure we don't overrun the dst buffer */
149                 if ((tmpdst - dst) >= dstsize)
150                         return -ENOBUFS;
151
152                 hi = hex_to_bin(*src++);
153
154                 /* did we get an odd number of characters? */
155                 if (!*src)
156                         return -EINVAL;
157                 lo = hex_to_bin(*src++);
158
159                 /* one of the characters isn't a hex digit */
160                 if (hi < 0 || lo < 0)
161                         return -EINVAL;
162
163                 /* now place it in the dst buffer */
164                 *tmpdst++ = (hi << 4) | lo;
165         }
166
167         return (ssize_t)(tmpdst - dst);
168 }
169
170 /*
171  * This program will almost always be run with root privileges since the
172  * kernel will call out to run it. Drop all capabilities prior to doing
173  * anything important to limit the exposure to potential compromise.
174  *
175  * FIXME: should we setuid to a different user early on instead?
176  */
177 static int
178 cltrack_set_caps(void)
179 {
180         int ret = 0;
181 #ifdef HAVE_SYS_CAPABILITY_H
182         unsigned long i;
183         cap_t caps;
184
185         /* prune the bounding set to nothing */
186         for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0 ; ++i) {
187                 ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
188                 if (ret) {
189                         xlog(L_ERROR, "Unable to prune capability %lu from "
190                                       "bounding set: %m", i);
191                         return -errno;
192                 }
193         }
194
195         /* get a blank capset */
196         caps = cap_init();
197         if (caps == NULL) {
198                 xlog(L_ERROR, "Unable to get blank capability set: %m");
199                 return -errno;
200         }
201
202         /* reset the process capabilities */
203         if (cap_set_proc(caps) != 0) {
204                 xlog(L_ERROR, "Unable to set process capabilities: %m");
205                 ret = -errno;
206         }
207         cap_free(caps);
208 #endif
209         return ret;
210 }
211
212 static int
213 cltrack_init(const char __attribute__((unused)) *unused)
214 {
215         int ret;
216
217         /*
218          * see if the storagedir is writable by root w/o CAP_DAC_OVERRIDE.
219          * If it isn't then give the user a warning but proceed as if
220          * everything is OK. If the DB has already been created, then
221          * everything might still work. If it doesn't exist at all, then
222          * assume that the maindb init will be able to create it. Fail on
223          * anything else.
224          */
225         if (access(storagedir, W_OK) == -1) {
226                 switch (errno) {
227                 case EACCES:
228                         xlog(L_WARNING, "Storage directory %s is not writable. "
229                                         "Should be owned by root and writable "
230                                         "by owner!", storagedir);
231                         break;
232                 case ENOENT:
233                         /* ignore and assume that we can create dir as root */
234                         break;
235                 default:
236                         xlog(L_ERROR, "Unexpected error when checking access "
237                                       "on %s: %m", storagedir);
238                         return -errno;
239                 }
240         }
241
242         /* set up storage db */
243         ret = sqlite_maindb_init(storagedir);
244         if (ret) {
245                 xlog(L_ERROR, "Failed to init database: %d", ret);
246                 /*
247                  * Convert any error here into -EACCES. It's not truly
248                  * accurate in all cases, but it should cause the kernel to
249                  * stop upcalling until the problem is resolved.
250                  */
251                 ret = -EACCES;
252         }
253         return ret;
254 }
255
256 static int
257 cltrack_create(const char *id)
258 {
259         int ret;
260         ssize_t len;
261
262         xlog(D_GENERAL, "%s: create client record.", __func__);
263
264         ret = sqlite_prepare_dbh(storagedir);
265         if (ret)
266                 return ret;
267
268         len = hex_str_to_bin(id, blob, sizeof(blob));
269         if (len < 0)
270                 return (int)len;
271
272         ret = sqlite_insert_client(blob, len);
273
274         return ret ? -EREMOTEIO : ret;
275 }
276
277 static int
278 cltrack_remove(const char *id)
279 {
280         int ret;
281         ssize_t len;
282
283         xlog(D_GENERAL, "%s: remove client record.", __func__);
284
285         ret = sqlite_prepare_dbh(storagedir);
286         if (ret)
287                 return ret;
288
289         len = hex_str_to_bin(id, blob, sizeof(blob));
290         if (len < 0)
291                 return (int)len;
292
293         ret = sqlite_remove_client(blob, len);
294
295         return ret ? -EREMOTEIO : ret;
296 }
297
298 static int
299 cltrack_check(const char *id)
300 {
301         int ret;
302         ssize_t len;
303
304         xlog(D_GENERAL, "%s: check client record", __func__);
305
306         ret = sqlite_prepare_dbh(storagedir);
307         if (ret)
308                 return ret;
309
310         len = hex_str_to_bin(id, blob, sizeof(blob));
311         if (len < 0)
312                 return (int)len;
313
314         ret = sqlite_check_client(blob, len);
315
316         return ret ? -EPERM : ret;
317 }
318
319 static int
320 cltrack_gracedone(const char *timestr)
321 {
322         int ret;
323         char *tail;
324         time_t gracetime;
325
326
327         ret = sqlite_prepare_dbh(storagedir);
328         if (ret)
329                 return ret;
330
331         errno = 0;
332         gracetime = strtol(timestr, &tail, 0);
333
334         /* did the resulting value overflow? (Probably -ERANGE here) */
335         if (errno)
336                 return -errno;
337
338         /* string wasn't fully converted */
339         if (*tail)
340                 return -EINVAL;
341
342         xlog(D_GENERAL, "%s: grace done. gracetime=%ld", __func__, gracetime);
343
344         ret = sqlite_remove_unreclaimed(gracetime);
345
346         return ret ? -EREMOTEIO : ret;
347 }
348
349 static struct cltrack_cmd *
350 find_cmd(char *cmdname)
351 {
352         struct cltrack_cmd *current = &commands[0];
353
354         while (current->name) {
355                 if (!strcmp(cmdname, current->name))
356                         return current;
357                 ++current;
358         }
359
360         xlog(L_ERROR, "%s: '%s' doesn't match any known command",
361                         __func__, cmdname);
362         return NULL;
363 }
364
365 int
366 main(int argc, char **argv)
367 {
368         char arg;
369         int rc = 0;
370         char *progname, *cmdarg = NULL;
371         struct cltrack_cmd *cmd;
372
373         progname = basename(argv[0]);
374
375         xlog_syslog(1);
376         xlog_stderr(0);
377
378         /* process command-line options */
379         while ((arg = getopt_long(argc, argv, "hdfs:", longopts,
380                                   NULL)) != EOF) {
381                 switch (arg) {
382                 case 'd':
383                         xlog_config(D_ALL, 1);
384                 case 'f':
385                         xlog_syslog(0);
386                         xlog_stderr(1);
387                         break;
388                 case 's':
389                         storagedir = optarg;
390                         break;
391                 default:
392                         usage(progname);
393                         return 0;
394                 }
395         }
396
397         xlog_open(progname);
398
399         /* we expect a command, at least */
400         if (optind >= argc) {
401                 xlog(L_ERROR, "Missing command name\n");
402                 rc = -EINVAL;
403                 goto out;
404         }
405
406         /* drop all capabilities */
407         rc = cltrack_set_caps();
408         if (rc)
409                 goto out;
410
411         cmd = find_cmd(argv[optind]);
412         if (!cmd) {
413                 /*
414                  * In the event that we get a command that we don't understand
415                  * then return a distinct error. The kernel can use this to
416                  * determine a new kernel/old userspace situation and cope
417                  * with it.
418                  */
419                 rc = -ENOSYS;
420                 goto out;
421         }
422
423         /* populate arg var if command needs it */
424         if (cmd->needs_arg) {
425                 if (optind + 1 >= argc) {
426                         xlog(L_ERROR, "Command %s requires an argument\n",
427                                 cmd->name);
428                         rc = -EINVAL;
429                         goto out;
430                 }
431                 cmdarg = argv[optind + 1];
432         }
433         rc = cmd->func(cmdarg);
434 out:
435         return rc;
436 }