]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
shell-completion: prevent mangling unit names
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
ec863ba6
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
ec863ba6 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stdbool.h>
23#include <errno.h>
24#include <string.h>
25#include <sys/socket.h>
26#include <sys/un.h>
27#include <stddef.h>
28#include <sys/poll.h>
29#include <sys/inotify.h>
30#include <unistd.h>
31#include <getopt.h>
b9ba604e 32#include <sys/signalfd.h>
7af53310 33#include <fcntl.h>
ec863ba6
LP
34
35#include "util.h"
49e942b2 36#include "mkdir.h"
9eb977db 37#include "path-util.h"
ec863ba6
LP
38#include "conf-parser.h"
39#include "utmp-wtmp.h"
e5ebf783 40#include "socket-util.h"
7f4e0805 41#include "ask-password-api.h"
21bc923a 42#include "strv.h"
c52f663b 43#include "build.h"
ec863ba6
LP
44
45static enum {
46 ACTION_LIST,
47 ACTION_QUERY,
48 ACTION_WATCH,
49 ACTION_WALL
50} arg_action = ACTION_QUERY;
51
e5ebf783 52static bool arg_plymouth = false;
0cf84693 53static bool arg_console = false;
e5ebf783 54
21bc923a
LP
55static int ask_password_plymouth(
56 const char *message,
57 usec_t until,
58 const char *flag_file,
59 bool accept_cached,
60 char ***_passphrases) {
61
e5ebf783 62 int fd = -1, notify = -1;
b92bea5d 63 union sockaddr_union sa = {};
e5ebf783
LP
64 char *packet = NULL;
65 ssize_t k;
66 int r, n;
b92bea5d 67 struct pollfd pollfd[2] = {};
e5ebf783
LP
68 char buffer[LINE_MAX];
69 size_t p = 0;
70 enum {
71 POLL_SOCKET,
72 POLL_INOTIFY
73 };
74
21bc923a
LP
75 assert(_passphrases);
76
e5ebf783
LP
77 if (flag_file) {
78 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
79 r = -errno;
80 goto finish;
81 }
82
83 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
84 r = -errno;
85 goto finish;
86 }
87 }
88
89 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
90 r = -errno;
91 goto finish;
92 }
93
e5ebf783 94 sa.sa.sa_family = AF_UNIX;
96707269
LP
95 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
96 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
c0f9c7da 97 log_error("Failed to connect to Plymouth: %m");
e5ebf783
LP
98 r = -errno;
99 goto finish;
100 }
101
21bc923a
LP
102 if (accept_cached) {
103 packet = strdup("c");
104 n = 1;
105 } else
106 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
107
108 if (!packet) {
e5ebf783
LP
109 r = -ENOMEM;
110 goto finish;
111 }
112
113 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
114 r = k < 0 ? (int) k : -EIO;
115 goto finish;
116 }
117
e5ebf783
LP
118 pollfd[POLL_SOCKET].fd = fd;
119 pollfd[POLL_SOCKET].events = POLLIN;
120 pollfd[POLL_INOTIFY].fd = notify;
121 pollfd[POLL_INOTIFY].events = POLLIN;
122
123 for (;;) {
124 int sleep_for = -1, j;
125
126 if (until > 0) {
127 usec_t y;
128
129 y = now(CLOCK_MONOTONIC);
130
131 if (y > until) {
ccc80078 132 r = -ETIME;
e5ebf783
LP
133 goto finish;
134 }
135
136 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
137 }
138
139 if (flag_file)
140 if (access(flag_file, F_OK) < 0) {
141 r = -errno;
142 goto finish;
143 }
144
145 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
146
147 if (errno == EINTR)
148 continue;
149
150 r = -errno;
151 goto finish;
152 } else if (j == 0) {
ccc80078 153 r = -ETIME;
e5ebf783
LP
154 goto finish;
155 }
156
157 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
158 flush_fd(notify);
159
160 if (pollfd[POLL_SOCKET].revents == 0)
161 continue;
162
163 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
164 r = k < 0 ? -errno : -EIO;
165 goto finish;
166 }
167
168 p += k;
169
170 if (p < 1)
171 continue;
172
173 if (buffer[0] == 5) {
21bc923a
LP
174
175 if (accept_cached) {
176 /* Hmm, first try with cached
177 * passwords failed, so let's retry
178 * with a normal password request */
179 free(packet);
180 packet = NULL;
181
182 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
183 r = -ENOMEM;
184 goto finish;
185 }
186
187 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
188 r = k < 0 ? (int) k : -EIO;
189 goto finish;
190 }
191
192 accept_cached = false;
193 p = 0;
194 continue;
195 }
196
e5ebf783
LP
197 /* No password, because UI not shown */
198 r = -ENOENT;
199 goto finish;
200
21bc923a 201 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 202 uint32_t size;
21bc923a 203 char **l;
e5ebf783 204
21bc923a 205 /* One ore more answers */
e5ebf783
LP
206 if (p < 5)
207 continue;
208
209 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 210 size = le32toh(size);
e5ebf783
LP
211 if (size+5 > sizeof(buffer)) {
212 r = -EIO;
213 goto finish;
214 }
215
216 if (p-5 < size)
217 continue;
218
21bc923a 219 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
e5ebf783
LP
220 r = -ENOMEM;
221 goto finish;
222 }
223
21bc923a 224 *_passphrases = l;
e5ebf783 225 break;
21bc923a 226
e5ebf783
LP
227 } else {
228 /* Unknown packet */
229 r = -EIO;
230 goto finish;
231 }
232 }
233
234 r = 0;
235
236finish:
03e334a1
LP
237 safe_close(notify);
238 safe_close(fd);
e5ebf783
LP
239
240 free(packet);
241
242 return r;
243}
244
0ddf1d3a 245static int parse_password(const char *filename, char **wall) {
e46eab86 246 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
ec863ba6
LP
247 uint64_t not_after = 0;
248 unsigned pid = 0;
21bc923a 249 bool accept_cached = false;
ec863ba6 250
f975e971
LP
251 const ConfigTableItem items[] = {
252 { "Ask", "Socket", config_parse_string, 0, &socket_name },
253 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
254 { "Ask", "Message", config_parse_string, 0, &message },
255 { "Ask", "PID", config_parse_unsigned, 0, &pid },
256 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
257 { NULL, NULL, NULL, 0, NULL }
ec863ba6
LP
258 };
259
ec863ba6 260 int r;
ec863ba6
LP
261
262 assert(filename);
263
36f822c4
ZJS
264 r = config_parse(NULL, filename, NULL,
265 NULL,
266 config_item_table_lookup, items,
267 true, false, true, NULL);
268 if (r < 0)
269 return r;
ec863ba6 270
7dcda352 271 if (!socket_name) {
ec863ba6 272 log_error("Invalid password file %s", filename);
e46eab86 273 return -EBADMSG;
ec863ba6
LP
274 }
275
e46eab86
ZJS
276 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
277 return 0;
ec863ba6 278
e46eab86
ZJS
279 if (pid > 0 && !pid_is_alive(pid))
280 return 0;
ded80335 281
ec863ba6
LP
282 if (arg_action == ACTION_LIST)
283 printf("'%s' (PID %u)\n", message, pid);
e46eab86 284
ec863ba6 285 else if (arg_action == ACTION_WALL) {
0ddf1d3a 286 char *_wall;
ec863ba6 287
0ddf1d3a
LP
288 if (asprintf(&_wall,
289 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 290 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
291 *wall ? *wall : "",
292 *wall ? "\r\n\r\n" : "",
ec863ba6 293 message,
e46eab86
ZJS
294 pid) < 0)
295 return log_oom();
ec863ba6 296
0ddf1d3a
LP
297 free(*wall);
298 *wall = _wall;
e46eab86 299
ec863ba6 300 } else {
e46eab86 301 union sockaddr_union sa = {};
3414abee 302 size_t packet_length = 0;
e46eab86 303 _cleanup_close_ int socket_fd = -1;
ec863ba6
LP
304
305 assert(arg_action == ACTION_QUERY ||
306 arg_action == ACTION_WATCH);
307
308 if (access(socket_name, W_OK) < 0) {
ec863ba6
LP
309 if (arg_action == ACTION_QUERY)
310 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
311
e46eab86 312 return 0;
ec863ba6
LP
313 }
314
21bc923a 315 if (arg_plymouth) {
cb90460e 316 _cleanup_strv_free_ char **passwords = NULL;
21bc923a 317
e46eab86
ZJS
318 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
319 if (r >= 0) {
21bc923a
LP
320 char **p;
321
322 packet_length = 1;
323 STRV_FOREACH(p, passwords)
324 packet_length += strlen(*p) + 1;
325
e46eab86
ZJS
326 packet = new(char, packet_length);
327 if (!packet)
21bc923a
LP
328 r = -ENOMEM;
329 else {
330 char *d;
331
332 packet[0] = '+';
333 d = packet+1;
334
335 STRV_FOREACH(p, passwords)
336 d = stpcpy(d, *p) + 1;
337 }
338 }
339
340 } else {
0cf84693 341 int tty_fd = -1;
e46eab86 342 _cleanup_free_ char *password = NULL;
0cf84693 343
e46eab86
ZJS
344 if (arg_console) {
345 tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1);
346 if (tty_fd < 0)
347 return tty_fd;
348 }
0cf84693 349
e5ebf783
LP
350 r = ask_password_tty(message, not_after, filename, &password);
351
0cf84693 352 if (arg_console) {
03e334a1 353 safe_close(tty_fd);
0cf84693
LP
354 release_terminal();
355 }
21bc923a 356
9726f9ff 357 if (r >= 0) {
e46eab86
ZJS
358 packet_length = 1 + strlen(password) + 1;
359 packet = new(char, packet_length);
360 if (!packet)
9726f9ff
LP
361 r = -ENOMEM;
362 else {
363 packet[0] = '+';
e46eab86 364 strcpy(packet + 1, password);
9726f9ff 365 }
5ba7b871 366 }
0cf84693
LP
367 }
368
e46eab86 369 if (r == -ETIME || r == -ENOENT)
446f0046 370 /* If the query went away, that's OK */
e46eab86 371 return 0;
446f0046 372
e5ebf783
LP
373 if (r < 0) {
374 log_error("Failed to query password: %s", strerror(-r));
e46eab86 375 return r;
ec863ba6
LP
376 }
377
e46eab86
ZJS
378 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
379 if (socket_fd < 0) {
ec863ba6 380 log_error("socket(): %m");
e46eab86 381 return -errno;
ec863ba6
LP
382 }
383
ec863ba6
LP
384 sa.un.sun_family = AF_UNIX;
385 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
386
e46eab86
ZJS
387 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
388 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
389 if (r < 0) {
ec863ba6 390 log_error("Failed to send: %m");
e46eab86 391 return r;
ec863ba6
LP
392 }
393 }
394
e46eab86 395 return 0;
ec863ba6
LP
396}
397
0cf84693 398static int wall_tty_block(void) {
7af53310 399 char *p;
fc116c6a
LP
400 int fd, r;
401 dev_t devnr;
7af53310 402
4d6d6518
LP
403 r = get_ctty_devnr(0, &devnr);
404 if (r < 0)
ee0e4cca 405 return r;
7af53310 406
2b583ce6 407 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
7af53310
LP
408 return -ENOMEM;
409
d2e54fae 410 mkdir_parents_label(p, 0700);
7af53310
LP
411 mkfifo(p, 0600);
412
413 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
414 free(p);
415
416 if (fd < 0)
417 return -errno;
418
419 return fd;
420}
421
0cf84693 422static bool wall_tty_match(const char *path) {
fc116c6a 423 int fd, k;
7af53310 424 char *p;
fc116c6a
LP
425 struct stat st;
426
427 if (path_is_absolute(path))
428 k = lstat(path, &st);
429 else {
430 if (asprintf(&p, "/dev/%s", path) < 0)
431 return true;
432
433 k = lstat(p, &st);
434 free(p);
435 }
436
437 if (k < 0)
438 return true;
439
440 if (!S_ISCHR(st.st_mode))
441 return true;
7af53310
LP
442
443 /* We use named pipes to ensure that wall messages suggesting
444 * password entry are not printed over password prompts
445 * already shown. We use the fact here that opening a pipe in
446 * non-blocking mode for write-only will succeed only if
447 * there's some writer behind it. Using pipes has the
448 * advantage that the block will automatically go away if the
449 * process dies. */
450
2b583ce6 451 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
7af53310
LP
452 return true;
453
454 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
455 free(p);
456
457 if (fd < 0)
458 return true;
459
460 /* What, we managed to open the pipe? Then this tty is filtered. */
03e334a1 461 safe_close(fd);
7af53310
LP
462 return false;
463}
464
ec863ba6
LP
465static int show_passwords(void) {
466 DIR *d;
467 struct dirent *de;
468 int r = 0;
469
2b583ce6 470 if (!(d = opendir("/run/systemd/ask-password"))) {
ec863ba6
LP
471 if (errno == ENOENT)
472 return 0;
473
267b3e41 474 log_error("opendir(/run/systemd/ask-password): %m");
ec863ba6
LP
475 return -errno;
476 }
477
478 while ((de = readdir(d))) {
479 char *p;
480 int q;
0ddf1d3a 481 char *wall;
ec863ba6 482
1a6f4df6
LP
483 /* We only support /dev on tmpfs, hence we can rely on
484 * d_type to be reliable */
485
ec863ba6
LP
486 if (de->d_type != DT_REG)
487 continue;
488
489 if (ignore_file(de->d_name))
490 continue;
491
492 if (!startswith(de->d_name, "ask."))
493 continue;
494
2b583ce6 495 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
0d0f0c50 496 r = log_oom();
ec863ba6
LP
497 goto finish;
498 }
499
0ddf1d3a
LP
500 wall = NULL;
501 if ((q = parse_password(p, &wall)) < 0)
ec863ba6
LP
502 r = q;
503
504 free(p);
0ddf1d3a
LP
505
506 if (wall) {
9003d9b0 507 utmp_wall(wall, NULL, wall_tty_match);
0ddf1d3a
LP
508 free(wall);
509 }
ec863ba6
LP
510 }
511
512finish:
513 if (d)
514 closedir(d);
515
516 return r;
517}
518
519static int watch_passwords(void) {
b9ba604e
LP
520 enum {
521 FD_INOTIFY,
522 FD_SIGNAL,
523 _FD_MAX
524 };
525
7af53310 526 int notify = -1, signal_fd = -1, tty_block_fd = -1;
b92bea5d 527 struct pollfd pollfd[_FD_MAX] = {};
b9ba604e 528 sigset_t mask;
ec863ba6
LP
529 int r;
530
0cf84693 531 tty_block_fd = wall_tty_block();
7af53310 532
d2e54fae 533 mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6
LP
534
535 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
536 r = -errno;
537 goto finish;
538 }
539
2b583ce6 540 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
ec863ba6
LP
541 r = -errno;
542 goto finish;
543 }
544
b9ba604e
LP
545 assert_se(sigemptyset(&mask) == 0);
546 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
547 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
548
549 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
550 log_error("signalfd(): %m");
551 r = -errno;
552 goto finish;
553 }
554
b9ba604e
LP
555 pollfd[FD_INOTIFY].fd = notify;
556 pollfd[FD_INOTIFY].events = POLLIN;
557 pollfd[FD_SIGNAL].fd = signal_fd;
558 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
559
560 for (;;) {
561 if ((r = show_passwords()) < 0)
0cf84693 562 log_error("Failed to show password: %s", strerror(-r));
ec863ba6 563
b9ba604e 564 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
565
566 if (errno == EINTR)
567 continue;
568
569 r = -errno;
570 goto finish;
571 }
572
b9ba604e 573 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 574 flush_fd(notify);
b9ba604e
LP
575
576 if (pollfd[FD_SIGNAL].revents != 0)
577 break;
ec863ba6
LP
578 }
579
580 r = 0;
581
582finish:
03e334a1
LP
583 safe_close(notify);
584 safe_close(signal_fd);
585 safe_close(tty_block_fd);
7af53310 586
ec863ba6
LP
587 return r;
588}
589
590static int help(void) {
591
592 printf("%s [OPTIONS...]\n\n"
593 "Process system password requests.\n\n"
e5ebf783 594 " -h --help Show this help\n"
c52f663b 595 " --version Show package version\n"
e5ebf783
LP
596 " --list Show pending password requests\n"
597 " --query Process pending password requests\n"
35b8ca3a
HH
598 " --watch Continuously process password requests\n"
599 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
600 " --plymouth Ask question with Plymouth instead of on TTY\n"
601 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6
LP
602 program_invocation_short_name);
603
604 return 0;
605}
606
607static int parse_argv(int argc, char *argv[]) {
608
609 enum {
610 ARG_LIST = 0x100,
611 ARG_QUERY,
612 ARG_WATCH,
613 ARG_WALL,
0cf84693 614 ARG_PLYMOUTH,
c52f663b
LP
615 ARG_CONSOLE,
616 ARG_VERSION
ec863ba6
LP
617 };
618
619 static const struct option options[] = {
e5ebf783 620 { "help", no_argument, NULL, 'h' },
c52f663b 621 { "version", no_argument, NULL, ARG_VERSION },
e5ebf783
LP
622 { "list", no_argument, NULL, ARG_LIST },
623 { "query", no_argument, NULL, ARG_QUERY },
624 { "watch", no_argument, NULL, ARG_WATCH },
625 { "wall", no_argument, NULL, ARG_WALL },
626 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
0cf84693 627 { "console", no_argument, NULL, ARG_CONSOLE },
eb9da376 628 {}
ec863ba6
LP
629 };
630
631 int c;
632
633 assert(argc >= 0);
634 assert(argv);
635
636 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
637
638 switch (c) {
639
640 case 'h':
eb9da376 641 return help();
ec863ba6 642
c52f663b
LP
643 case ARG_VERSION:
644 puts(PACKAGE_STRING);
c52f663b
LP
645 puts(SYSTEMD_FEATURES);
646 return 0;
647
ec863ba6
LP
648 case ARG_LIST:
649 arg_action = ACTION_LIST;
650 break;
651
652 case ARG_QUERY:
653 arg_action = ACTION_QUERY;
654 break;
655
656 case ARG_WATCH:
657 arg_action = ACTION_WATCH;
658 break;
659
660 case ARG_WALL:
661 arg_action = ACTION_WALL;
662 break;
663
e5ebf783
LP
664 case ARG_PLYMOUTH:
665 arg_plymouth = true;
666 break;
667
0cf84693
LP
668 case ARG_CONSOLE:
669 arg_console = true;
670 break;
671
ec863ba6
LP
672 case '?':
673 return -EINVAL;
674
675 default:
eb9da376 676 assert_not_reached("Unhandled option");
ec863ba6
LP
677 }
678 }
679
680 if (optind != argc) {
681 help();
682 return -EINVAL;
683 }
684
685 return 1;
686}
687
688int main(int argc, char *argv[]) {
689 int r;
690
4b261568 691 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
692 log_parse_environment();
693 log_open();
694
4c12626c
LP
695 umask(0022);
696
ec863ba6
LP
697 if ((r = parse_argv(argc, argv)) <= 0)
698 goto finish;
699
0cf84693
LP
700 if (arg_console) {
701 setsid();
702 release_terminal();
703 }
704
ec863ba6
LP
705 if (arg_action == ACTION_WATCH ||
706 arg_action == ACTION_WALL)
707 r = watch_passwords();
708 else
709 r = show_passwords();
710
96707269
LP
711 if (r < 0)
712 log_error("Error: %s", strerror(-r));
713
ec863ba6
LP
714finish:
715 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
716}