Annotation of src/usr.bin/sudo/check.c, Revision 1.21
1.1 millert 1: /*
1.19 millert 2: * Copyright (c) 1993-1996,1998-2005, 2007-2009
1.16 millert 3: * Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 4: *
1.12 millert 5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
1.1 millert 8: *
1.12 millert 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.11 millert 16: *
17: * Sponsored in part by the Defense Advanced Research Projects
18: * Agency (DARPA) and Air Force Research Laboratory, Air Force
19: * Materiel Command, USAF, under agreement number F39502-99-1-0512.
1.1 millert 20: */
21:
1.14 millert 22: #include <config.h>
1.1 millert 23:
1.6 millert 24: #include <sys/types.h>
25: #include <sys/param.h>
26: #include <sys/stat.h>
1.12 millert 27: #ifndef __TANDEM
28: # include <sys/file.h>
29: #endif
1.1 millert 30: #include <stdio.h>
31: #ifdef STDC_HEADERS
1.6 millert 32: # include <stdlib.h>
33: # include <stddef.h>
34: #else
35: # ifdef HAVE_STDLIB_H
36: # include <stdlib.h>
37: # endif
1.1 millert 38: #endif /* STDC_HEADERS */
1.6 millert 39: #ifdef HAVE_STRING_H
40: # include <string.h>
41: #else
42: # ifdef HAVE_STRINGS_H
43: # include <strings.h>
44: # endif
45: #endif /* HAVE_STRING_H */
1.1 millert 46: #ifdef HAVE_UNISTD_H
1.6 millert 47: # include <unistd.h>
1.1 millert 48: #endif /* HAVE_UNISTD_H */
49: #include <errno.h>
50: #include <fcntl.h>
51: #include <signal.h>
52: #include <time.h>
53: #include <pwd.h>
54: #include <grp.h>
1.14 millert 55: #ifndef HAVE_TIMESPEC
56: # include <emul/timespec.h>
57: #endif
1.1 millert 58:
59: #include "sudo.h"
60:
61: /* Status codes for timestamp_status() */
62: #define TS_CURRENT 0
63: #define TS_OLD 1
64: #define TS_MISSING 2
65: #define TS_NOFILE 3
66: #define TS_ERROR 4
67:
1.14 millert 68: /* Flags for timestamp_status() */
69: #define TS_MAKE_DIRS 1
70: #define TS_REMOVE 2
71:
1.1 millert 72: static void build_timestamp __P((char **, char **));
73: static int timestamp_status __P((char *, char *, char *, int));
74: static char *expand_prompt __P((char *, char *, char *));
1.12 millert 75: static void lecture __P((int));
1.1 millert 76: static void update_timestamp __P((char *, char *));
77:
78: /*
79: * This function only returns if the user can successfully
1.12 millert 80: * verify who he/she is.
1.1 millert 81: */
82: void
1.18 millert 83: check_user(validated, mode)
1.14 millert 84: int validated;
1.18 millert 85: int mode;
1.1 millert 86: {
87: char *timestampdir = NULL;
88: char *timestampfile = NULL;
89: char *prompt;
90: int status;
91:
1.18 millert 92: if (mode & MODE_INVALIDATE) {
93: /* do not check or update timestamp */
94: status = TS_ERROR;
95: } else {
1.21 ! millert 96: /*
! 97: * Don't prompt for the root passwd or if the user is exempt.
! 98: * If the user is not changing uid/gid, no need for a password.
! 99: */
! 100: if (user_uid == 0 || (user_uid == runas_pw->pw_uid &&
! 101: (!runas_gr || user_gid == runas_gr->gr_gid)) || user_is_exempt())
1.18 millert 102: return;
1.1 millert 103:
1.18 millert 104: build_timestamp(×tampdir, ×tampfile);
105: status = timestamp_status(timestampdir, timestampfile, user_name,
1.14 millert 106: TS_MAKE_DIRS);
1.18 millert 107: }
1.14 millert 108: if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) {
1.16 millert 109: /* Bail out if we are non-interactive and a password is required */
1.18 millert 110: if (ISSET(mode, MODE_NONINTERACTIVE))
1.16 millert 111: errorx(1, "sorry, a password is required to run %s", getprogname());
112:
113: /* If user specified -A, make sure we have an askpass helper. */
114: if (ISSET(tgetpass_flags, TGP_ASKPASS)) {
115: if (user_askpass == NULL)
116: log_error(NO_MAIL,
117: "no askpass program specified, try setting SUDO_ASKPASS");
1.17 millert 118: } else if (!ISSET(tgetpass_flags, TGP_STDIN)) {
1.16 millert 119: /* If no tty but DISPLAY is set, use askpass if we have it. */
1.17 millert 120: if (!user_ttypath && !tty_present()) {
1.16 millert 121: if (user_askpass && user_display && *user_display != '\0') {
122: SET(tgetpass_flags, TGP_ASKPASS);
123: } else if (!def_visiblepw) {
124: log_error(NO_MAIL,
125: "no tty present and no askpass program specified");
126: }
127: }
128: }
129:
130: if (!ISSET(tgetpass_flags, TGP_ASKPASS))
131: lecture(status);
1.1 millert 132:
133: /* Expand any escapes in the prompt. */
1.12 millert 134: prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
1.1 millert 135: user_name, user_shost);
136:
1.4 millert 137: verify_user(auth_pw, prompt);
1.1 millert 138: }
1.14 millert 139: /* Only update timestamp if user was validated. */
140: if (status != TS_ERROR && ISSET(validated, VALIDATE_OK))
1.1 millert 141: update_timestamp(timestampdir, timestampfile);
1.14 millert 142: efree(timestampdir);
143: efree(timestampfile);
1.1 millert 144: }
145:
146: /*
147: * Standard sudo lecture.
148: */
149: static void
1.12 millert 150: lecture(status)
151: int status;
1.1 millert 152: {
1.12 millert 153: FILE *fp;
154: char buf[BUFSIZ];
155: ssize_t nread;
156:
157: if (def_lecture == never ||
158: (def_lecture == once && status != TS_MISSING && status != TS_ERROR))
159: return;
1.1 millert 160:
1.12 millert 161: if (def_lecture_file && (fp = fopen(def_lecture_file, "r")) != NULL) {
162: while ((nread = fread(buf, sizeof(char), sizeof(buf), fp)) != 0)
163: fwrite(buf, nread, 1, stderr);
1.14 millert 164: fclose(fp);
1.12 millert 165: } else {
1.1 millert 166: (void) fputs("\n\
167: We trust you have received the usual lecture from the local System\n\
1.12 millert 168: Administrator. It usually boils down to these three things:\n\
1.1 millert 169: \n\
1.12 millert 170: #1) Respect the privacy of others.\n\
171: #2) Think before you type.\n\
172: #3) With great power comes great responsibility.\n\n",
173: stderr);
1.1 millert 174: }
175: }
176:
177: /*
178: * Update the time on the timestamp file/dir or create it if necessary.
179: */
180: static void
181: update_timestamp(timestampdir, timestampfile)
182: char *timestampdir;
183: char *timestampfile;
184: {
1.8 millert 185: if (timestamp_uid != 0)
186: set_perms(PERM_TIMESTAMP);
1.12 millert 187: if (touch(-1, timestampfile ? timestampfile : timestampdir, NULL) == -1) {
1.1 millert 188: if (timestampfile) {
189: int fd = open(timestampfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
190:
191: if (fd == -1)
192: log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
193: else
194: close(fd);
195: } else {
196: if (mkdir(timestampdir, 0700) == -1)
197: log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
198: }
199: }
1.8 millert 200: if (timestamp_uid != 0)
201: set_perms(PERM_ROOT);
1.1 millert 202: }
203:
204: /*
205: * Expand %h and %u escapes in the prompt and pass back the dynamically
206: * allocated result. Returns the same string if there are no escapes.
207: */
208: static char *
209: expand_prompt(old_prompt, user, host)
210: char *old_prompt;
211: char *user;
212: char *host;
213: {
1.8 millert 214: size_t len, n;
1.1 millert 215: int subst;
1.8 millert 216: char *p, *np, *new_prompt, *endp;
1.1 millert 217:
218: /* How much space do we need to malloc for the prompt? */
219: subst = 0;
1.8 millert 220: for (p = old_prompt, len = strlen(old_prompt); *p; p++) {
221: if (p[0] =='%') {
222: switch (p[1]) {
223: case 'h':
224: p++;
225: len += strlen(user_shost) - 2;
226: subst = 1;
227: break;
228: case 'H':
229: p++;
230: len += strlen(user_host) - 2;
231: subst = 1;
232: break;
1.15 millert 233: case 'p':
234: p++;
235: if (def_rootpw)
236: len += 2;
237: else if (def_targetpw || def_runaspw)
1.16 millert 238: len += strlen(runas_pw->pw_name) - 2;
1.15 millert 239: else
240: len += strlen(user_name) - 2;
241: subst = 1;
242: break;
1.8 millert 243: case 'u':
244: p++;
245: len += strlen(user_name) - 2;
246: subst = 1;
247: break;
248: case 'U':
249: p++;
1.16 millert 250: len += strlen(runas_pw->pw_name) - 2;
1.8 millert 251: subst = 1;
252: break;
253: case '%':
254: p++;
255: len--;
256: subst = 1;
257: break;
258: default:
259: break;
1.1 millert 260: }
261: }
262: }
263:
264: if (subst) {
1.8 millert 265: new_prompt = (char *) emalloc(++len);
1.9 millert 266: endp = new_prompt + len;
1.8 millert 267: for (p = old_prompt, np = new_prompt; *p; p++) {
268: if (p[0] =='%') {
269: switch (p[1]) {
270: case 'h':
271: p++;
1.9 millert 272: n = strlcpy(np, user_shost, np - endp);
273: if (n >= np - endp)
1.8 millert 274: goto oflow;
275: np += n;
276: continue;
277: case 'H':
278: p++;
1.9 millert 279: n = strlcpy(np, user_host, np - endp);
280: if (n >= np - endp)
1.8 millert 281: goto oflow;
1.15 millert 282: np += n;
283: continue;
284: case 'p':
285: p++;
286: if (def_rootpw)
287: n = strlcpy(np, "root", np - endp);
288: else if (def_targetpw || def_runaspw)
1.16 millert 289: n = strlcpy(np, runas_pw->pw_name, np - endp);
1.15 millert 290: else
291: n = strlcpy(np, user_name, np - endp);
292: if (n >= np - endp)
293: goto oflow;
1.8 millert 294: np += n;
295: continue;
296: case 'u':
297: p++;
1.9 millert 298: n = strlcpy(np, user_name, np - endp);
299: if (n >= np - endp)
1.8 millert 300: goto oflow;
301: np += n;
302: continue;
303: case 'U':
304: p++;
1.16 millert 305: n = strlcpy(np, runas_pw->pw_name, np - endp);
1.9 millert 306: if (n >= np - endp)
1.8 millert 307: goto oflow;
308: np += n;
309: continue;
310: case '%':
311: /* convert %% -> % */
312: p++;
313: break;
314: default:
315: /* no conversion */
316: break;
1.1 millert 317: }
1.8 millert 318: }
1.9 millert 319: *np++ = *p;
1.8 millert 320: if (np >= endp)
321: goto oflow;
1.1 millert 322: }
323: *np = '\0';
324: } else
325: new_prompt = old_prompt;
326:
327: return(new_prompt);
1.8 millert 328:
329: oflow:
330: /* We pre-allocate enough space, so this should never happen. */
1.16 millert 331: errorx(1, "internal error, expand_prompt() overflow");
1.1 millert 332: }
333:
334: /*
335: * Checks if the user is exempt from supplying a password.
336: */
337: int
338: user_is_exempt()
339: {
340: struct group *grp;
341: char **gr_mem;
342:
1.12 millert 343: if (!def_exempt_group)
1.1 millert 344: return(FALSE);
345:
1.16 millert 346: if (!(grp = sudo_getgrnam(def_exempt_group)))
1.1 millert 347: return(FALSE);
348:
1.5 millert 349: if (user_gid == grp->gr_gid)
1.1 millert 350: return(TRUE);
351:
352: for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) {
353: if (strcmp(user_name, *gr_mem) == 0)
354: return(TRUE);
355: }
356:
357: return(FALSE);
358: }
359:
360: /*
361: * Fills in timestampdir as well as timestampfile if using tty tickets.
362: */
363: static void
364: build_timestamp(timestampdir, timestampfile)
365: char **timestampdir;
366: char **timestampfile;
367: {
1.4 millert 368: char *dirparent;
369: int len;
1.1 millert 370:
1.12 millert 371: dirparent = def_timestampdir;
1.4 millert 372: len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
1.12 millert 373: if (len >= PATH_MAX)
1.13 millert 374: log_error(0, "timestamp path too long: %s", *timestampdir);
1.4 millert 375:
376: /*
377: * Timestamp file may be a file in the directory or NUL to use
378: * the directory as the timestamp.
379: */
1.12 millert 380: if (def_tty_tickets) {
1.1 millert 381: char *p;
382:
383: if ((p = strrchr(user_tty, '/')))
384: p++;
385: else
386: p = user_tty;
1.12 millert 387: if (def_targetpw)
1.4 millert 388: len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
1.16 millert 389: p, runas_pw->pw_name);
1.4 millert 390: else
391: len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
1.12 millert 392: if (len >= PATH_MAX)
1.13 millert 393: log_error(0, "timestamp path too long: %s", *timestampfile);
1.12 millert 394: } else if (def_targetpw) {
1.4 millert 395: len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
1.16 millert 396: runas_pw->pw_name);
1.12 millert 397: if (len >= PATH_MAX)
1.13 millert 398: log_error(0, "timestamp path too long: %s", *timestampfile);
1.4 millert 399: } else
1.1 millert 400: *timestampfile = NULL;
401: }
402:
403: /*
404: * Check the timestamp file and directory and return their status.
405: */
406: static int
1.14 millert 407: timestamp_status(timestampdir, timestampfile, user, flags)
1.1 millert 408: char *timestampdir;
409: char *timestampfile;
410: char *user;
1.14 millert 411: int flags;
1.1 millert 412: {
413: struct stat sb;
414: time_t now;
1.12 millert 415: char *dirparent = def_timestampdir;
1.1 millert 416: int status = TS_ERROR; /* assume the worst */
417:
1.8 millert 418: if (timestamp_uid != 0)
419: set_perms(PERM_TIMESTAMP);
420:
1.1 millert 421: /*
422: * Sanity check dirparent and make it if it doesn't already exist.
423: * We start out assuming the worst (that the dir is not sane) and
424: * if it is ok upgrade the status to ``no timestamp file''.
425: * Note that we don't check the parent(s) of dirparent for
426: * sanity since the sudo dir is often just located in /tmp.
427: */
428: if (lstat(dirparent, &sb) == 0) {
429: if (!S_ISDIR(sb.st_mode))
430: log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
1.14 millert 431: dirparent, (unsigned int) sb.st_mode);
1.8 millert 432: else if (sb.st_uid != timestamp_uid)
433: log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
434: dirparent, (unsigned long) sb.st_uid,
435: (unsigned long) timestamp_uid);
1.1 millert 436: else if ((sb.st_mode & 0000022))
437: log_error(NO_EXIT,
438: "%s writable by non-owner (0%o), should be mode 0700",
1.14 millert 439: dirparent, (unsigned int) sb.st_mode);
1.1 millert 440: else {
441: if ((sb.st_mode & 0000777) != 0700)
442: (void) chmod(dirparent, 0700);
443: status = TS_MISSING;
444: }
445: } else if (errno != ENOENT) {
446: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
447: } else {
448: /* No dirparent, try to make one. */
1.14 millert 449: if (ISSET(flags, TS_MAKE_DIRS)) {
1.1 millert 450: if (mkdir(dirparent, S_IRWXU))
451: log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
452: dirparent);
453: else
454: status = TS_MISSING;
455: }
456: }
1.8 millert 457: if (status == TS_ERROR) {
458: if (timestamp_uid != 0)
459: set_perms(PERM_ROOT);
1.1 millert 460: return(status);
1.8 millert 461: }
1.1 millert 462:
463: /*
464: * Sanity check the user's ticket dir. We start by downgrading
465: * the status to TS_ERROR. If the ticket dir exists and is sane
466: * this will be upgraded to TS_OLD. If the dir does not exist,
467: * it will be upgraded to TS_MISSING.
468: */
469: status = TS_ERROR; /* downgrade status again */
470: if (lstat(timestampdir, &sb) == 0) {
471: if (!S_ISDIR(sb.st_mode)) {
472: if (S_ISREG(sb.st_mode)) {
473: /* convert from old style */
474: if (unlink(timestampdir) == 0)
475: status = TS_MISSING;
476: } else
477: log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
1.14 millert 478: timestampdir, (unsigned int) sb.st_mode);
1.8 millert 479: } else if (sb.st_uid != timestamp_uid)
480: log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
481: timestampdir, (unsigned long) sb.st_uid,
482: (unsigned long) timestamp_uid);
1.1 millert 483: else if ((sb.st_mode & 0000022))
484: log_error(NO_EXIT,
485: "%s writable by non-owner (0%o), should be mode 0700",
1.14 millert 486: timestampdir, (unsigned int) sb.st_mode);
1.1 millert 487: else {
488: if ((sb.st_mode & 0000777) != 0700)
489: (void) chmod(timestampdir, 0700);
490: status = TS_OLD; /* do date check later */
491: }
492: } else if (errno != ENOENT) {
493: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
494: } else
495: status = TS_MISSING;
496:
497: /*
498: * If there is no user ticket dir, AND we are in tty ticket mode,
1.14 millert 499: * AND the TS_MAKE_DIRS flag is set, create the user ticket dir.
1.1 millert 500: */
1.14 millert 501: if (status == TS_MISSING && timestampfile && ISSET(flags, TS_MAKE_DIRS)) {
1.1 millert 502: if (mkdir(timestampdir, S_IRWXU) == -1) {
503: status = TS_ERROR;
504: log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
505: }
506: }
507:
508: /*
509: * Sanity check the tty ticket file if it exists.
510: */
511: if (timestampfile && status != TS_ERROR) {
512: if (status != TS_MISSING)
513: status = TS_NOFILE; /* dir there, file missing */
514: if (lstat(timestampfile, &sb) == 0) {
515: if (!S_ISREG(sb.st_mode)) {
516: status = TS_ERROR;
517: log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
1.14 millert 518: timestampfile, (unsigned int) sb.st_mode);
1.1 millert 519: } else {
520: /* If bad uid or file mode, complain and kill the bogus file. */
1.8 millert 521: if (sb.st_uid != timestamp_uid) {
1.1 millert 522: log_error(NO_EXIT,
1.13 millert 523: "%s owned by uid %lu, should be uid %lu",
1.8 millert 524: timestampfile, (unsigned long) sb.st_uid,
525: (unsigned long) timestamp_uid);
1.1 millert 526: (void) unlink(timestampfile);
527: } else if ((sb.st_mode & 0000022)) {
528: log_error(NO_EXIT,
529: "%s writable by non-owner (0%o), should be mode 0600",
1.14 millert 530: timestampfile, (unsigned int) sb.st_mode);
1.1 millert 531: (void) unlink(timestampfile);
532: } else {
533: /* If not mode 0600, fix it. */
534: if ((sb.st_mode & 0000777) != 0600)
535: (void) chmod(timestampfile, 0600);
536:
537: status = TS_OLD; /* actually check mtime below */
538: }
539: }
540: } else if (errno != ENOENT) {
541: log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
542: status = TS_ERROR;
543: }
544: }
545:
546: /*
1.14 millert 547: * If the file/dir exists and we are not removing it, check its mtime.
1.1 millert 548: */
1.14 millert 549: if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) {
1.6 millert 550: /* Negative timeouts only expire manually (sudo -k). */
1.12 millert 551: if (def_timestamp_timeout < 0 && sb.st_mtime != 0)
1.6 millert 552: status = TS_CURRENT;
553: else {
1.12 millert 554: /* XXX - should use timespec here */
1.6 millert 555: now = time(NULL);
1.12 millert 556: if (def_timestamp_timeout &&
557: now - sb.st_mtime < 60 * def_timestamp_timeout) {
1.6 millert 558: /*
559: * Check for bogus time on the stampfile. The clock may
560: * have been set back or someone could be trying to spoof us.
561: */
1.12 millert 562: if (sb.st_mtime > now + 60 * def_timestamp_timeout * 2) {
1.6 millert 563: log_error(NO_EXIT,
564: "timestamp too far in the future: %20.20s",
565: 4 + ctime(&sb.st_mtime));
566: if (timestampfile)
567: (void) unlink(timestampfile);
568: else
569: (void) rmdir(timestampdir);
570: status = TS_MISSING;
571: } else
572: status = TS_CURRENT;
573: }
1.1 millert 574: }
575: }
576:
1.8 millert 577: if (timestamp_uid != 0)
578: set_perms(PERM_ROOT);
1.1 millert 579: return(status);
580: }
581:
582: /*
583: * Remove the timestamp ticket file/dir.
584: */
585: void
586: remove_timestamp(remove)
587: int remove;
588: {
1.12 millert 589: struct timespec ts;
590: char *timestampdir, *timestampfile, *path;
1.1 millert 591: int status;
592:
593: build_timestamp(×tampdir, ×tampfile);
1.14 millert 594: status = timestamp_status(timestampdir, timestampfile, user_name,
595: TS_REMOVE);
1.1 millert 596: if (status == TS_OLD || status == TS_CURRENT) {
1.12 millert 597: path = timestampfile ? timestampfile : timestampdir;
1.1 millert 598: if (remove) {
599: if (timestampfile)
600: status = unlink(timestampfile);
601: else
602: status = rmdir(timestampdir);
1.2 millert 603: if (status == -1 && errno != ENOENT) {
1.10 millert 604: log_error(NO_EXIT, "can't remove %s (%s), will reset to Epoch",
1.12 millert 605: path, strerror(errno));
1.1 millert 606: remove = FALSE;
607: }
1.12 millert 608: } else {
609: timespecclear(&ts);
610: if (touch(-1, path, &ts) == -1)
1.16 millert 611: error(1, "can't reset %s to Epoch", path);
1.1 millert 612: }
613: }
614:
1.14 millert 615: efree(timestampdir);
616: efree(timestampfile);
1.1 millert 617: }