00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "dbus-sysdeps.h"
00025 #include "dbus-internals.h"
00026 #include "dbus-protocol.h"
00027 #include "dbus-string.h"
00028 #define DBUS_USERDB_INCLUDES_PRIVATE 1
00029 #include "dbus-userdb.h"
00030 #include "dbus-test.h"
00031
00032 #include <sys/types.h>
00033 #include <stdlib.h>
00034 #include <string.h>
00035 #include <signal.h>
00036 #include <unistd.h>
00037 #include <stdio.h>
00038 #include <syslog.h>
00039 #include <errno.h>
00040 #include <fcntl.h>
00041 #include <sys/stat.h>
00042 #include <grp.h>
00043 #include <sys/socket.h>
00044 #include <dirent.h>
00045 #include <sys/un.h>
00046
00047 #ifndef O_BINARY
00048 #define O_BINARY 0
00049 #endif
00050
00064 dbus_bool_t
00065 _dbus_become_daemon (const DBusString *pidfile,
00066 int print_pid_fd,
00067 DBusError *error)
00068 {
00069 const char *s;
00070 pid_t child_pid;
00071 int dev_null_fd;
00072
00073 _dbus_verbose ("Becoming a daemon...\n");
00074
00075 _dbus_verbose ("chdir to /\n");
00076 if (chdir ("/") < 0)
00077 {
00078 dbus_set_error (error, DBUS_ERROR_FAILED,
00079 "Could not chdir() to root directory");
00080 return FALSE;
00081 }
00082
00083 _dbus_verbose ("forking...\n");
00084 switch ((child_pid = fork ()))
00085 {
00086 case -1:
00087 _dbus_verbose ("fork failed\n");
00088 dbus_set_error (error, _dbus_error_from_errno (errno),
00089 "Failed to fork daemon: %s", _dbus_strerror (errno));
00090 return FALSE;
00091 break;
00092
00093 case 0:
00094 _dbus_verbose ("in child, closing std file descriptors\n");
00095
00096
00097
00098
00099
00100
00101 dev_null_fd = open ("/dev/null", O_RDWR);
00102 if (dev_null_fd >= 0)
00103 {
00104 dup2 (dev_null_fd, 0);
00105 dup2 (dev_null_fd, 1);
00106
00107 s = _dbus_getenv ("DBUS_DEBUG_OUTPUT");
00108 if (s == NULL || *s == '\0')
00109 dup2 (dev_null_fd, 2);
00110 else
00111 _dbus_verbose ("keeping stderr open due to DBUS_DEBUG_OUTPUT\n");
00112 }
00113
00114
00115 _dbus_verbose ("setting umask\n");
00116 umask (022);
00117 break;
00118
00119 default:
00120 if (pidfile)
00121 {
00122 _dbus_verbose ("parent writing pid file\n");
00123 if (!_dbus_write_pid_file (pidfile,
00124 child_pid,
00125 error))
00126 {
00127 _dbus_verbose ("pid file write failed, killing child\n");
00128 kill (child_pid, SIGTERM);
00129 return FALSE;
00130 }
00131 }
00132
00133
00134 if (print_pid_fd >= 0)
00135 {
00136 DBusString pid;
00137 int bytes;
00138
00139 if (!_dbus_string_init (&pid))
00140 {
00141 _DBUS_SET_OOM (error);
00142 kill (child_pid, SIGTERM);
00143 return FALSE;
00144 }
00145
00146 if (!_dbus_string_append_int (&pid, child_pid) ||
00147 !_dbus_string_append (&pid, "\n"))
00148 {
00149 _dbus_string_free (&pid);
00150 _DBUS_SET_OOM (error);
00151 kill (child_pid, SIGTERM);
00152 return FALSE;
00153 }
00154
00155 bytes = _dbus_string_get_length (&pid);
00156 if (_dbus_write (print_pid_fd, &pid, 0, bytes) != bytes)
00157 {
00158 dbus_set_error (error, DBUS_ERROR_FAILED,
00159 "Printing message bus PID: %s\n",
00160 _dbus_strerror (errno));
00161 _dbus_string_free (&pid);
00162 kill (child_pid, SIGTERM);
00163 return FALSE;
00164 }
00165
00166 _dbus_string_free (&pid);
00167 }
00168 _dbus_verbose ("parent exiting\n");
00169 _exit (0);
00170 break;
00171 }
00172
00173 _dbus_verbose ("calling setsid()\n");
00174 if (setsid () == -1)
00175 _dbus_assert_not_reached ("setsid() failed");
00176
00177 return TRUE;
00178 }
00179
00180
00189 dbus_bool_t
00190 _dbus_write_pid_file (const DBusString *filename,
00191 unsigned long pid,
00192 DBusError *error)
00193 {
00194 const char *cfilename;
00195 int fd;
00196 FILE *f;
00197
00198 cfilename = _dbus_string_get_const_data (filename);
00199
00200 fd = open (cfilename, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0644);
00201
00202 if (fd < 0)
00203 {
00204 dbus_set_error (error, _dbus_error_from_errno (errno),
00205 "Failed to open \"%s\": %s", cfilename,
00206 _dbus_strerror (errno));
00207 return FALSE;
00208 }
00209
00210 if ((f = fdopen (fd, "w")) == NULL)
00211 {
00212 dbus_set_error (error, _dbus_error_from_errno (errno),
00213 "Failed to fdopen fd %d: %s", fd, _dbus_strerror (errno));
00214 close (fd);
00215 return FALSE;
00216 }
00217
00218 if (fprintf (f, "%lu\n", pid) < 0)
00219 {
00220 dbus_set_error (error, _dbus_error_from_errno (errno),
00221 "Failed to write to \"%s\": %s", cfilename,
00222 _dbus_strerror (errno));
00223 return FALSE;
00224 }
00225
00226 if (fclose (f) == EOF)
00227 {
00228 dbus_set_error (error, _dbus_error_from_errno (errno),
00229 "Failed to close \"%s\": %s", cfilename,
00230 _dbus_strerror (errno));
00231 return FALSE;
00232 }
00233
00234 return TRUE;
00235 }
00236
00237
00246 dbus_bool_t
00247 _dbus_change_identity (dbus_uid_t uid,
00248 dbus_gid_t gid,
00249 DBusError *error)
00250 {
00251
00252
00253
00254
00255
00256
00257 if (setgroups (0, NULL) < 0)
00258 _dbus_warn ("Failed to drop supplementary groups: %s\n",
00259 _dbus_strerror (errno));
00260
00261
00262
00263
00264 if (setgid (gid) < 0)
00265 {
00266 dbus_set_error (error, _dbus_error_from_errno (errno),
00267 "Failed to set GID to %lu: %s", gid,
00268 _dbus_strerror (errno));
00269 return FALSE;
00270 }
00271
00272 if (setuid (uid) < 0)
00273 {
00274 dbus_set_error (error, _dbus_error_from_errno (errno),
00275 "Failed to set UID to %lu: %s", uid,
00276 _dbus_strerror (errno));
00277 return FALSE;
00278 }
00279
00280 return TRUE;
00281 }
00282
00283 void
00284 _dbus_init_system_log (void)
00285 {
00286 openlog ("dbus", LOG_PID, LOG_DAEMON);
00287 }
00288
00296 void
00297 _dbus_log_info (const char *msg, va_list args)
00298 {
00299 vsyslog (LOG_DAEMON|LOG_NOTICE, msg, args);
00300 }
00301
00309 void
00310 _dbus_log_security (const char *msg, va_list args)
00311 {
00312 vsyslog (LOG_AUTH|LOG_NOTICE, msg, args);
00313 }
00314
00320 void
00321 _dbus_set_signal_handler (int sig,
00322 DBusSignalHandler handler)
00323 {
00324 struct sigaction act;
00325 sigset_t empty_mask;
00326
00327 sigemptyset (&empty_mask);
00328 act.sa_handler = handler;
00329 act.sa_mask = empty_mask;
00330 act.sa_flags = 0;
00331 sigaction (sig, &act, 0);
00332 }
00333
00334
00342 dbus_bool_t
00343 _dbus_delete_directory (const DBusString *filename,
00344 DBusError *error)
00345 {
00346 const char *filename_c;
00347
00348 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
00349
00350 filename_c = _dbus_string_get_const_data (filename);
00351
00352 if (rmdir (filename_c) != 0)
00353 {
00354 dbus_set_error (error, DBUS_ERROR_FAILED,
00355 "Failed to remove directory %s: %s\n",
00356 filename_c, _dbus_strerror (errno));
00357 return FALSE;
00358 }
00359
00360 return TRUE;
00361 }
00362
00368 dbus_bool_t
00369 _dbus_file_exists (const char *file)
00370 {
00371 return (access (file, F_OK) == 0);
00372 }
00373
00380 dbus_bool_t
00381 _dbus_user_at_console (const char *username,
00382 DBusError *error)
00383 {
00384
00385 DBusString f;
00386 dbus_bool_t result;
00387
00388 result = FALSE;
00389 if (!_dbus_string_init (&f))
00390 {
00391 _DBUS_SET_OOM (error);
00392 return FALSE;
00393 }
00394
00395 if (!_dbus_string_append (&f, DBUS_CONSOLE_AUTH_DIR))
00396 {
00397 _DBUS_SET_OOM (error);
00398 goto out;
00399 }
00400
00401
00402 if (!_dbus_string_append (&f, username))
00403 {
00404 _DBUS_SET_OOM (error);
00405 goto out;
00406 }
00407
00408 result = _dbus_file_exists (_dbus_string_get_const_data (&f));
00409
00410 out:
00411 _dbus_string_free (&f);
00412
00413 return result;
00414 }
00415
00416
00423 dbus_bool_t
00424 _dbus_path_is_absolute (const DBusString *filename)
00425 {
00426 if (_dbus_string_get_length (filename) > 0)
00427 return _dbus_string_get_byte (filename, 0) == '/';
00428 else
00429 return FALSE;
00430 }
00431
00440 dbus_bool_t
00441 _dbus_stat (const DBusString *filename,
00442 DBusStat *statbuf,
00443 DBusError *error)
00444 {
00445 const char *filename_c;
00446 struct stat sb;
00447
00448 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
00449
00450 filename_c = _dbus_string_get_const_data (filename);
00451
00452 if (stat (filename_c, &sb) < 0)
00453 {
00454 dbus_set_error (error, _dbus_error_from_errno (errno),
00455 "%s", _dbus_strerror (errno));
00456 return FALSE;
00457 }
00458
00459 statbuf->mode = sb.st_mode;
00460 statbuf->nlink = sb.st_nlink;
00461 statbuf->uid = sb.st_uid;
00462 statbuf->gid = sb.st_gid;
00463 statbuf->size = sb.st_size;
00464 statbuf->atime = sb.st_atime;
00465 statbuf->mtime = sb.st_mtime;
00466 statbuf->ctime = sb.st_ctime;
00467
00468 return TRUE;
00469 }
00470
00471
00475 struct DBusDirIter
00476 {
00477 DIR *d;
00479 };
00480
00488 DBusDirIter*
00489 _dbus_directory_open (const DBusString *filename,
00490 DBusError *error)
00491 {
00492 DIR *d;
00493 DBusDirIter *iter;
00494 const char *filename_c;
00495
00496 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
00497
00498 filename_c = _dbus_string_get_const_data (filename);
00499
00500 d = opendir (filename_c);
00501 if (d == NULL)
00502 {
00503 dbus_set_error (error, _dbus_error_from_errno (errno),
00504 "Failed to read directory \"%s\": %s",
00505 filename_c,
00506 _dbus_strerror (errno));
00507 return NULL;
00508 }
00509 iter = dbus_new0 (DBusDirIter, 1);
00510 if (iter == NULL)
00511 {
00512 closedir (d);
00513 dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
00514 "Could not allocate memory for directory iterator");
00515 return NULL;
00516 }
00517
00518 iter->d = d;
00519
00520 return iter;
00521 }
00522
00536 dbus_bool_t
00537 _dbus_directory_get_next_file (DBusDirIter *iter,
00538 DBusString *filename,
00539 DBusError *error)
00540 {
00541 struct dirent *ent;
00542
00543 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
00544
00545 again:
00546 errno = 0;
00547 ent = readdir (iter->d);
00548 if (ent == NULL)
00549 {
00550 if (errno != 0)
00551 dbus_set_error (error,
00552 _dbus_error_from_errno (errno),
00553 "%s", _dbus_strerror (errno));
00554 return FALSE;
00555 }
00556 else if (ent->d_name[0] == '.' &&
00557 (ent->d_name[1] == '\0' ||
00558 (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
00559 goto again;
00560 else
00561 {
00562 _dbus_string_set_length (filename, 0);
00563 if (!_dbus_string_append (filename, ent->d_name))
00564 {
00565 dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
00566 "No memory to read directory entry");
00567 return FALSE;
00568 }
00569 else
00570 return TRUE;
00571 }
00572 }
00573
00577 void
00578 _dbus_directory_close (DBusDirIter *iter)
00579 {
00580 closedir (iter->d);
00581 dbus_free (iter);
00582 }
00583
00584 static dbus_bool_t
00585 fill_user_info_from_group (struct group *g,
00586 DBusGroupInfo *info,
00587 DBusError *error)
00588 {
00589 _dbus_assert (g->gr_name != NULL);
00590
00591 info->gid = g->gr_gid;
00592 info->groupname = _dbus_strdup (g->gr_name);
00593
00594
00595
00596 if (info->groupname == NULL)
00597 {
00598 dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
00599 return FALSE;
00600 }
00601
00602 return TRUE;
00603 }
00604
00605 static dbus_bool_t
00606 fill_group_info (DBusGroupInfo *info,
00607 dbus_gid_t gid,
00608 const DBusString *groupname,
00609 DBusError *error)
00610 {
00611 const char *group_c_str;
00612
00613 _dbus_assert (groupname != NULL || gid != DBUS_GID_UNSET);
00614 _dbus_assert (groupname == NULL || gid == DBUS_GID_UNSET);
00615
00616 if (groupname)
00617 group_c_str = _dbus_string_get_const_data (groupname);
00618 else
00619 group_c_str = NULL;
00620
00621
00622
00623
00624
00625
00626 #if defined (HAVE_POSIX_GETPWNAM_R) || defined (HAVE_NONPOSIX_GETPWNAM_R)
00627 {
00628 struct group *g;
00629 int result;
00630 char buf[1024];
00631 struct group g_str;
00632
00633 g = NULL;
00634 #ifdef HAVE_POSIX_GETPWNAM_R
00635
00636 if (group_c_str)
00637 result = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf),
00638 &g);
00639 else
00640 result = getgrgid_r (gid, &g_str, buf, sizeof (buf),
00641 &g);
00642 #else
00643 g = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf));
00644 result = 0;
00645 #endif
00646 if (result == 0 && g == &g_str)
00647 {
00648 return fill_user_info_from_group (g, info, error);
00649 }
00650 else
00651 {
00652 dbus_set_error (error, _dbus_error_from_errno (errno),
00653 "Group %s unknown or failed to look it up\n",
00654 group_c_str ? group_c_str : "???");
00655 return FALSE;
00656 }
00657 }
00658 #else
00659 {
00660
00661 struct group *g;
00662
00663 g = getgrnam (group_c_str);
00664
00665 if (g != NULL)
00666 {
00667 return fill_user_info_from_group (g, info, error);
00668 }
00669 else
00670 {
00671 dbus_set_error (error, _dbus_error_from_errno (errno),
00672 "Group %s unknown or failed to look it up\n",
00673 group_c_str ? group_c_str : "???");
00674 return FALSE;
00675 }
00676 }
00677 #endif
00678 }
00679
00689 dbus_bool_t
00690 _dbus_group_info_fill (DBusGroupInfo *info,
00691 const DBusString *groupname,
00692 DBusError *error)
00693 {
00694 return fill_group_info (info, DBUS_GID_UNSET,
00695 groupname, error);
00696
00697 }
00698
00708 dbus_bool_t
00709 _dbus_group_info_fill_gid (DBusGroupInfo *info,
00710 dbus_gid_t gid,
00711 DBusError *error)
00712 {
00713 return fill_group_info (info, gid, NULL, error);
00714 }
00715
00721 void
00722 _dbus_group_info_free (DBusGroupInfo *info)
00723 {
00724 dbus_free (info->groupname);
00725 }
00726
00728
00740 dbus_bool_t
00741 _dbus_string_get_dirname (const DBusString *filename,
00742 DBusString *dirname)
00743 {
00744 int sep;
00745
00746 _dbus_assert (filename != dirname);
00747 _dbus_assert (filename != NULL);
00748 _dbus_assert (dirname != NULL);
00749
00750
00751 sep = _dbus_string_get_length (filename);
00752 if (sep == 0)
00753 return _dbus_string_append (dirname, ".");
00754
00755 while (sep > 0 && _dbus_string_get_byte (filename, sep - 1) == '/')
00756 --sep;
00757
00758 _dbus_assert (sep >= 0);
00759
00760 if (sep == 0)
00761 return _dbus_string_append (dirname, "/");
00762
00763
00764 _dbus_string_find_byte_backward (filename, sep, '/', &sep);
00765 if (sep < 0)
00766 return _dbus_string_append (dirname, ".");
00767
00768
00769 while (sep > 0 && _dbus_string_get_byte (filename, sep - 1) == '/')
00770 --sep;
00771
00772 _dbus_assert (sep >= 0);
00773
00774 if (sep == 0 &&
00775 _dbus_string_get_byte (filename, 0) == '/')
00776 return _dbus_string_append (dirname, "/");
00777 else
00778 return _dbus_string_copy_len (filename, 0, sep - 0,
00779 dirname, _dbus_string_get_length (dirname));
00780 }
00782
00783 static void
00784 string_squash_nonprintable (DBusString *str)
00785 {
00786 char *buf;
00787 int i, len;
00788
00789 buf = _dbus_string_get_data (str);
00790 len = _dbus_string_get_length (str);
00791
00792 for (i = 0; i < len; i++)
00793 if (buf[i] == '\0')
00794 buf[i] = ' ';
00795 else if (buf[i] < 0x20 || buf[i] > 127)
00796 buf[i] = '?';
00797 }
00798
00813 dbus_bool_t
00814 _dbus_command_for_pid (unsigned long pid,
00815 DBusString *str,
00816 int max_len,
00817 DBusError *error)
00818 {
00819
00820 DBusString path;
00821 DBusString cmdline;
00822 int fd;
00823
00824 if (!_dbus_string_init (&path))
00825 {
00826 _DBUS_SET_OOM (error);
00827 return FALSE;
00828 }
00829
00830 if (!_dbus_string_init (&cmdline))
00831 {
00832 _DBUS_SET_OOM (error);
00833 _dbus_string_free (&path);
00834 return FALSE;
00835 }
00836
00837 if (!_dbus_string_append_printf (&path, "/proc/%ld/cmdline", pid))
00838 goto oom;
00839
00840 fd = open (_dbus_string_get_const_data (&path), O_RDONLY);
00841 if (fd < 0)
00842 {
00843 dbus_set_error (error,
00844 _dbus_error_from_errno (errno),
00845 "Failed to open \"%s\": %s",
00846 _dbus_string_get_const_data (&path),
00847 _dbus_strerror (errno));
00848 goto fail;
00849 }
00850
00851 if (!_dbus_read (fd, &cmdline, max_len))
00852 {
00853 dbus_set_error (error,
00854 _dbus_error_from_errno (errno),
00855 "Failed to read from \"%s\": %s",
00856 _dbus_string_get_const_data (&path),
00857 _dbus_strerror (errno));
00858 goto fail;
00859 }
00860
00861 if (!_dbus_close (fd, error))
00862 goto fail;
00863
00864 string_squash_nonprintable (&cmdline);
00865
00866 if (!_dbus_string_copy (&cmdline, 0, str, _dbus_string_get_length (str)))
00867 goto oom;
00868
00869 _dbus_string_free (&cmdline);
00870 _dbus_string_free (&path);
00871 return TRUE;
00872 oom:
00873 _DBUS_SET_OOM (error);
00874 fail:
00875 _dbus_string_free (&cmdline);
00876 _dbus_string_free (&path);
00877 return FALSE;
00878 }
00879
00880 #ifdef DBUS_BUILD_TESTS
00881 #include <stdlib.h>
00882 static void
00883 check_dirname (const char *filename,
00884 const char *dirname)
00885 {
00886 DBusString f, d;
00887
00888 _dbus_string_init_const (&f, filename);
00889
00890 if (!_dbus_string_init (&d))
00891 _dbus_assert_not_reached ("no memory");
00892
00893 if (!_dbus_string_get_dirname (&f, &d))
00894 _dbus_assert_not_reached ("no memory");
00895
00896 if (!_dbus_string_equal_c_str (&d, dirname))
00897 {
00898 _dbus_warn ("For filename \"%s\" got dirname \"%s\" and expected \"%s\"\n",
00899 filename,
00900 _dbus_string_get_const_data (&d),
00901 dirname);
00902 exit (1);
00903 }
00904
00905 _dbus_string_free (&d);
00906 }
00907
00908 static void
00909 check_path_absolute (const char *path,
00910 dbus_bool_t expected)
00911 {
00912 DBusString p;
00913
00914 _dbus_string_init_const (&p, path);
00915
00916 if (_dbus_path_is_absolute (&p) != expected)
00917 {
00918 _dbus_warn ("For path \"%s\" expected absolute = %d got %d\n",
00919 path, expected, _dbus_path_is_absolute (&p));
00920 exit (1);
00921 }
00922 }
00923
00929 dbus_bool_t
00930 _dbus_sysdeps_test (void)
00931 {
00932 DBusString str;
00933 double val;
00934 int pos;
00935
00936 check_dirname ("foo", ".");
00937 check_dirname ("foo/bar", "foo");
00938 check_dirname ("foo//bar", "foo");
00939 check_dirname ("foo///bar", "foo");
00940 check_dirname ("foo/bar/", "foo");
00941 check_dirname ("foo//bar/", "foo");
00942 check_dirname ("foo///bar/", "foo");
00943 check_dirname ("foo/bar//", "foo");
00944 check_dirname ("foo//bar////", "foo");
00945 check_dirname ("foo///bar///////", "foo");
00946 check_dirname ("/foo", "/");
00947 check_dirname ("////foo", "/");
00948 check_dirname ("/foo/bar", "/foo");
00949 check_dirname ("/foo//bar", "/foo");
00950 check_dirname ("/foo///bar", "/foo");
00951 check_dirname ("/", "/");
00952 check_dirname ("///", "/");
00953 check_dirname ("", ".");
00954
00955
00956 _dbus_string_init_const (&str, "3.5");
00957 if (!_dbus_string_parse_double (&str,
00958 0, &val, &pos))
00959 {
00960 _dbus_warn ("Failed to parse double");
00961 exit (1);
00962 }
00963 if (ABS(3.5 - val) > 1e-6)
00964 {
00965 _dbus_warn ("Failed to parse 3.5 correctly, got: %f", val);
00966 exit (1);
00967 }
00968 if (pos != 3)
00969 {
00970 _dbus_warn ("_dbus_string_parse_double of \"3.5\" returned wrong position %d", pos);
00971 exit (1);
00972 }
00973
00974 _dbus_string_init_const (&str, "0xff");
00975 if (!_dbus_string_parse_double (&str,
00976 0, &val, &pos))
00977 {
00978 _dbus_warn ("Failed to parse double");
00979 exit (1);
00980 }
00981 if (ABS (0xff - val) > 1e-6)
00982 {
00983 _dbus_warn ("Failed to parse 0xff correctly, got: %f\n", val);
00984 exit (1);
00985 }
00986 if (pos != 4)
00987 {
00988 _dbus_warn ("_dbus_string_parse_double of \"0xff\" returned wrong position %d", pos);
00989 exit (1);
00990 }
00991
00992 check_path_absolute ("/", TRUE);
00993 check_path_absolute ("/foo", TRUE);
00994 check_path_absolute ("", FALSE);
00995 check_path_absolute ("foo", FALSE);
00996 check_path_absolute ("foo/bar", FALSE);
00997
00998 return TRUE;
00999 }
01000 #endif