]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nfsdcltrack/nfsdcltrack.c
Imported upstream 1.2.8
[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 #include <dirent.h>
40 #ifdef HAVE_SYS_CAPABILITY_H
41 #include <sys/prctl.h>
42 #include <sys/capability.h>
43 #endif
44
45 #include "xlog.h"
46 #include "sqlite.h"
47
48 #ifndef CLD_DEFAULT_STORAGEDIR
49 #define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
50 #endif
51
52 /* defined by RFC 3530 */
53 #define NFS4_OPAQUE_LIMIT       1024
54
55 /* private data structures */
56 struct cltrack_cmd {
57         char *name;
58         bool needs_arg;
59         int (*func)(const char *arg);
60 };
61
62 /* forward declarations */
63 static int cltrack_init(const char *unused);
64 static int cltrack_create(const char *id);
65 static int cltrack_remove(const char *id);
66 static int cltrack_check(const char *id);
67 static int cltrack_gracedone(const char *gracetime);
68
69 /* global variables */
70 static struct option longopts[] =
71 {
72         { "help", 0, NULL, 'h' },
73         { "debug", 0, NULL, 'd' },
74         { "foreground", 0, NULL, 'f' },
75         { "storagedir", 1, NULL, 's' },
76         { NULL, 0, 0, 0 },
77 };
78
79 static struct cltrack_cmd commands[] =
80 {
81         { "init", false, cltrack_init },
82         { "create", true, cltrack_create },
83         { "remove", true, cltrack_remove },
84         { "check", true, cltrack_check },
85         { "gracedone", true, cltrack_gracedone },
86         { NULL, false, NULL },
87 };
88
89 static char *storagedir = CLD_DEFAULT_STORAGEDIR;
90
91 /* common buffer for holding id4 blobs */
92 static unsigned char blob[NFS4_OPAQUE_LIMIT];
93
94 static void
95 usage(char *progname)
96 {
97         printf("%s [ -hfd ] [ -s dir ] < cmd > < arg >\n", progname);
98         printf("Where < cmd > is one of the following and takes the following < arg >:\n");
99         printf("    init\n");
100         printf("    create <nfs_client_id4>\n");
101         printf("    remove <nfs_client_id4>\n");
102         printf("    check  <nfs_client_id4>\n");
103         printf("    gracedone <epoch time>\n");
104 }
105
106
107 /**
108  * hex_to_bin - convert a hex digit to its real value
109  * @ch: ascii character represents hex digit
110  *
111  * hex_to_bin() converts one hex digit to its actual value or -1 in case of bad
112  * input.
113  *
114  * Note: borrowed from lib/hexdump.c in the Linux kernel sources.
115  */
116 static int
117 hex_to_bin(char ch)
118 {
119         if ((ch >= '0') && (ch <= '9'))
120                 return ch - '0';
121         ch = tolower(ch);
122         if ((ch >= 'a') && (ch <= 'f'))
123                 return ch - 'a' + 10;
124         return -1;
125 }
126
127 /**
128  * hex_str_to_bin - convert a hexidecimal string into a binary blob
129  *
130  * @src: string of hex digit pairs
131  * @dst: destination buffer to hold binary data
132  * @dstsize: size of the destination buffer
133  *
134  * Walk a string of hex digit pairs and convert them into binary data. Returns
135  * the resulting length of the binary data or a negative error code. If the
136  * data will not fit in the buffer, it returns -ENOBUFS (but will likely have
137  * clobbered the dst buffer in the process of determining that). If there are
138  * non-hexidecimal characters in the src, or an odd number of them then it
139  * returns -EINVAL.
140  */
141 static ssize_t
142 hex_str_to_bin(const char *src, unsigned char *dst, ssize_t dstsize)
143 {
144         unsigned char *tmpdst = dst;
145
146         while (*src) {
147                 int hi, lo;
148
149                 /* make sure we don't overrun the dst buffer */
150                 if ((tmpdst - dst) >= dstsize)
151                         return -ENOBUFS;
152
153                 hi = hex_to_bin(*src++);
154
155                 /* did we get an odd number of characters? */
156                 if (!*src)
157                         return -EINVAL;
158                 lo = hex_to_bin(*src++);
159
160                 /* one of the characters isn't a hex digit */
161                 if (hi < 0 || lo < 0)
162                         return -EINVAL;
163
164                 /* now place it in the dst buffer */
165                 *tmpdst++ = (hi << 4) | lo;
166         }
167
168         return (ssize_t)(tmpdst - dst);
169 }
170
171 /*
172  * This program will almost always be run with root privileges since the
173  * kernel will call out to run it. Drop all capabilities prior to doing
174  * anything important to limit the exposure to potential compromise.
175  *
176  * FIXME: should we setuid to a different user early on instead?
177  */
178 static int
179 cltrack_set_caps(void)
180 {
181         int ret = 0;
182 #ifdef HAVE_SYS_CAPABILITY_H
183         unsigned long i;
184         cap_t caps;
185
186         /* prune the bounding set to nothing */
187         for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0 ; ++i) {
188                 ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
189                 if (ret) {
190                         xlog(L_ERROR, "Unable to prune capability %lu from "
191                                       "bounding set: %m", i);
192                         return -errno;
193                 }
194         }
195
196         /* get a blank capset */
197         caps = cap_init();
198         if (caps == NULL) {
199                 xlog(L_ERROR, "Unable to get blank capability set: %m");
200                 return -errno;
201         }
202
203         /* reset the process capabilities */
204         if (cap_set_proc(caps) != 0) {
205                 xlog(L_ERROR, "Unable to set process capabilities: %m");
206                 ret = -errno;
207         }
208         cap_free(caps);
209 #endif
210         return ret;
211 }
212
213 static int
214 cltrack_init(const char __attribute__((unused)) *unused)
215 {
216         int ret;
217
218         /*
219          * see if the storagedir is writable by root w/o CAP_DAC_OVERRIDE.
220          * If it isn't then give the user a warning but proceed as if
221          * everything is OK. If the DB has already been created, then
222          * everything might still work. If it doesn't exist at all, then
223          * assume that the maindb init will be able to create it. Fail on
224          * anything else.
225          */
226         if (access(storagedir, W_OK) == -1) {
227                 switch (errno) {
228                 case EACCES:
229                         xlog(L_WARNING, "Storage directory %s is not writable. "
230                                         "Should be owned by root and writable "
231                                         "by owner!", storagedir);
232                         break;
233                 case ENOENT:
234                         /* ignore and assume that we can create dir as root */
235                         break;
236                 default:
237                         xlog(L_ERROR, "Unexpected error when checking access "
238                                       "on %s: %m", storagedir);
239                         return -errno;
240                 }
241         }
242
243         /* set up storage db */
244         ret = sqlite_maindb_init(storagedir);
245         if (ret) {
246                 xlog(L_ERROR, "Failed to init database: %d", ret);
247                 /*
248                  * Convert any error here into -EACCES. It's not truly
249                  * accurate in all cases, but it should cause the kernel to
250                  * stop upcalling until the problem is resolved.
251                  */
252                 ret = -EACCES;
253         }
254         return ret;
255 }
256
257 static int
258 cltrack_create(const char *id)
259 {
260         int ret;
261         ssize_t len;
262
263         xlog(D_GENERAL, "%s: create client record.", __func__);
264
265         ret = sqlite_prepare_dbh(storagedir);
266         if (ret)
267                 return ret;
268
269         len = hex_str_to_bin(id, blob, sizeof(blob));
270         if (len < 0)
271                 return (int)len;
272
273         ret = sqlite_insert_client(blob, len);
274
275         return ret ? -EREMOTEIO : ret;
276 }
277
278 static int
279 cltrack_remove(const char *id)
280 {
281         int ret;
282         ssize_t len;
283
284         xlog(D_GENERAL, "%s: remove client record.", __func__);
285
286         ret = sqlite_prepare_dbh(storagedir);
287         if (ret)
288                 return ret;
289
290         len = hex_str_to_bin(id, blob, sizeof(blob));
291         if (len < 0)
292                 return (int)len;
293
294         ret = sqlite_remove_client(blob, len);
295
296         return ret ? -EREMOTEIO : ret;
297 }
298
299 static int
300 cltrack_check_legacy(const unsigned char *blob, const ssize_t len)
301 {
302         int ret;
303         struct stat st;
304         char *recdir = getenv("NFSDCLTRACK_LEGACY_RECDIR");
305
306         if (!recdir) {
307                 xlog(D_GENERAL, "No NFSDCLTRACK_LEGACY_RECDIR env var");
308                 return -EOPNOTSUPP;
309         }
310
311         /* fail recovery on any stat failure */
312         ret = stat(recdir, &st);
313         if (ret) {
314                 xlog(D_GENERAL, "Unable to stat %s: %d", recdir, errno);
315                 return -errno;
316         }
317
318         /* fail if it isn't a directory */
319         if (!S_ISDIR(st.st_mode)) {
320                 xlog(D_GENERAL, "%s is not a directory: mode=0%o", recdir
321                                 , st.st_mode);
322                 return -ENOTDIR;
323         }
324
325         /* Dir exists, try to insert record into db */
326         ret = sqlite_insert_client(blob, len);
327         if (ret) {
328                 xlog(D_GENERAL, "Failed to insert client: %d", ret);
329                 return -EREMOTEIO;
330         }
331
332         /* remove the legacy recoverydir */
333         ret = rmdir(recdir);
334         if (ret) {
335                 xlog(D_GENERAL, "Failed to rmdir %s: %d", recdir, errno);
336                 return -errno;
337         }
338         return 0;
339 }
340
341 static int
342 cltrack_check(const char *id)
343 {
344         int ret;
345         ssize_t len;
346
347         xlog(D_GENERAL, "%s: check client record", __func__);
348
349         ret = sqlite_prepare_dbh(storagedir);
350         if (ret)
351                 return ret;
352
353         len = hex_str_to_bin(id, blob, sizeof(blob));
354         if (len < 0)
355                 return (int)len;
356
357         ret = sqlite_check_client(blob, len);
358         if (ret)
359                 ret = cltrack_check_legacy(blob, len);
360
361         return ret ? -EPERM : ret;
362 }
363
364 /* Clean out the v4recoverydir -- best effort here */
365 static void
366 cltrack_legacy_gracedone(void)
367 {
368         DIR *v4recovery;
369         struct dirent *entry;
370         char *dirname = getenv("NFSDCLTRACK_LEGACY_TOPDIR");
371
372         if (!dirname)
373                 return;
374
375         v4recovery = opendir(dirname);
376         if (!v4recovery)
377                 return;
378
379         while ((entry = readdir(v4recovery))) {
380                 int len;
381
382                 /* skip "." and ".." */
383                 if (entry->d_name[0] == '.') {
384                         switch (entry->d_name[1]) {
385                         case '\0':
386                                 continue;
387                         case '.':
388                                 if (entry->d_name[2] == '\0')
389                                         continue;
390                         }
391                 }
392
393                 /* borrow the clientid blob for this */
394                 len = snprintf((char *)blob, sizeof(blob), "%s/%s", dirname,
395                                 entry->d_name);
396
397                 /* if there's a problem, then skip this entry */
398                 if (len < 0 || (size_t)len >= sizeof(blob)) {
399                         xlog(L_WARNING, "%s: unable to build filename for %s!",
400                                 __func__, entry->d_name);
401                         continue;
402                 }
403
404                 len = rmdir((char *)blob);
405                 if (len)
406                         xlog(L_WARNING, "%s: unable to rmdir %s: %d", __func__,
407                                 (char *)blob, len);
408         }
409
410         closedir(v4recovery);
411 }
412
413 static int
414 cltrack_gracedone(const char *timestr)
415 {
416         int ret;
417         char *tail;
418         time_t gracetime;
419
420
421         ret = sqlite_prepare_dbh(storagedir);
422         if (ret)
423                 return ret;
424
425         errno = 0;
426         gracetime = strtol(timestr, &tail, 0);
427
428         /* did the resulting value overflow? (Probably -ERANGE here) */
429         if (errno)
430                 return -errno;
431
432         /* string wasn't fully converted */
433         if (*tail)
434                 return -EINVAL;
435
436         xlog(D_GENERAL, "%s: grace done. gracetime=%ld", __func__, gracetime);
437
438         ret = sqlite_remove_unreclaimed(gracetime);
439
440         cltrack_legacy_gracedone();
441
442         return ret ? -EREMOTEIO : ret;
443 }
444
445 static struct cltrack_cmd *
446 find_cmd(char *cmdname)
447 {
448         struct cltrack_cmd *current = &commands[0];
449
450         while (current->name) {
451                 if (!strcmp(cmdname, current->name))
452                         return current;
453                 ++current;
454         }
455
456         xlog(L_ERROR, "%s: '%s' doesn't match any known command",
457                         __func__, cmdname);
458         return NULL;
459 }
460
461 int
462 main(int argc, char **argv)
463 {
464         char arg;
465         int rc = 0;
466         char *progname, *cmdarg = NULL;
467         struct cltrack_cmd *cmd;
468
469         progname = basename(argv[0]);
470
471         xlog_syslog(1);
472         xlog_stderr(0);
473
474         /* process command-line options */
475         while ((arg = getopt_long(argc, argv, "hdfs:", longopts,
476                                   NULL)) != EOF) {
477                 switch (arg) {
478                 case 'd':
479                         xlog_config(D_ALL, 1);
480                 case 'f':
481                         xlog_syslog(0);
482                         xlog_stderr(1);
483                         break;
484                 case 's':
485                         storagedir = optarg;
486                         break;
487                 default:
488                         usage(progname);
489                         return 0;
490                 }
491         }
492
493         xlog_open(progname);
494
495         /* we expect a command, at least */
496         if (optind >= argc) {
497                 xlog(L_ERROR, "Missing command name\n");
498                 rc = -EINVAL;
499                 goto out;
500         }
501
502         /* drop all capabilities */
503         rc = cltrack_set_caps();
504         if (rc)
505                 goto out;
506
507         cmd = find_cmd(argv[optind]);
508         if (!cmd) {
509                 /*
510                  * In the event that we get a command that we don't understand
511                  * then return a distinct error. The kernel can use this to
512                  * determine a new kernel/old userspace situation and cope
513                  * with it.
514                  */
515                 rc = -ENOSYS;
516                 goto out;
517         }
518
519         /* populate arg var if command needs it */
520         if (cmd->needs_arg) {
521                 if (optind + 1 >= argc) {
522                         xlog(L_ERROR, "Command %s requires an argument\n",
523                                 cmd->name);
524                         rc = -EINVAL;
525                         goto out;
526                 }
527                 cmdarg = argv[optind + 1];
528         }
529         rc = cmd->func(cmdarg);
530 out:
531         return rc;
532 }