home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / gnu / uucp-1.04 / unix / xqtsub.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-02-13  |  14.4 KB  |  699 lines

  1. /* xqtsub.c
  2.    System dependent functions used only by uuxqt.
  3.  
  4.    Copyright (C) 1991, 1992 Ian Lance Taylor
  5.  
  6.    This file is part of the Taylor UUCP package.
  7.  
  8.    This program is free software; you can redistribute it and/or
  9.    modify it under the terms of the GNU General Public License as
  10.    published by the Free Software Foundation; either version 2 of the
  11.    License, or (at your option) any later version.
  12.  
  13.    This program 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
  16.    General Public License for more details.
  17.  
  18.    You should have received a copy of the GNU General Public License
  19.    along with this program; if not, write to the Free Software
  20.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21.  
  22.    The author of the program may be contacted at ian@airs.com or
  23.    c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254.
  24.    */
  25.  
  26. #include "uucp.h"
  27.  
  28. #if USE_RCS_ID
  29. const char xqtsub_rcsid[] = "$Id: xqtsub.c,v 1.8 1993/01/17 04:16:13 ian Rel $";
  30. #endif
  31.  
  32. #include "uudefs.h"
  33. #include "uuconf.h"
  34. #include "system.h"
  35. #include "sysdep.h"
  36.  
  37. #include <ctype.h>
  38. #include <errno.h>
  39.  
  40. #if HAVE_FCNTL_H
  41. #include <fcntl.h>
  42. #else
  43. #if HAVE_SYS_FILE_H
  44. #include <sys/file.h>
  45. #endif
  46. #endif
  47.  
  48. #ifndef O_RDONLY
  49. #define O_RDONLY 0
  50. #define O_WRONLY 1
  51. #define O_RDWR 2
  52. #endif
  53.  
  54. #ifndef O_NOCTTY
  55. #define O_NOCTTY 0
  56. #endif
  57.  
  58. #ifndef FD_CLOEXEC
  59. #define FD_CLOEXEC 1
  60. #endif
  61.  
  62. #if HAVE_OPENDIR
  63. #if HAVE_DIRENT_H
  64. #include <dirent.h>
  65. #else /* ! HAVE_DIRENT_H */
  66. #include <sys/dir.h>
  67. #define dirent direct
  68. #endif /* ! HAVE_DIRENT_H */
  69. #endif /* HAVE_OPENDIR */
  70.  
  71. /* Get a value for EX_TEMPFAIL.  */
  72.  
  73. #if HAVE_SYSEXITS_H
  74. #include <sysexits.h>
  75. #endif
  76.  
  77. #ifndef EX_TEMPFAIL
  78. #define EX_TEMPFAIL 75
  79. #endif
  80.  
  81. /* Get the full pathname of the command to execute, given the list of
  82.    permitted commands and the allowed path.  */
  83.  
  84. char *
  85. zsysdep_find_command (zcmd, pzcmds, pzpath, pferr)
  86.      const char *zcmd;
  87.      char **pzcmds;
  88.      char **pzpath;
  89.      boolean *pferr;
  90. {
  91.   char **pz;
  92.  
  93.   *pferr = FALSE;
  94.  
  95.   for (pz = pzcmds; *pz != NULL; pz++)
  96.     {
  97.       char *zslash;
  98.  
  99.       if (strcmp (*pz, "ALL") == 0)
  100.     break;
  101.  
  102.       zslash = strrchr (*pz, '/');
  103.       if (zslash != NULL)
  104.     ++zslash;
  105.       else
  106.     zslash = *pz;
  107.       if (strcmp (zslash, zcmd) == 0
  108.       || strcmp (*pz, zcmd) == 0)
  109.     {
  110.       /* If we already have an absolute path, we can get out
  111.          immediately.  */
  112.       if (**pz == '/')
  113.         return zbufcpy (*pz);
  114.       break;
  115.     }
  116.     }
  117.  
  118.   /* If we didn't find this command, get out.  */
  119.   if (*pz == NULL)
  120.     return NULL;
  121.  
  122.   /* We didn't find an absolute pathname, so we must look through
  123.      the path.  */
  124.   for (pz = pzpath; *pz != NULL; pz++)
  125.     {
  126.       char *zname;
  127.       struct stat s;
  128.  
  129.       zname = zsysdep_in_dir (*pz, zcmd);
  130.       if (stat (zname, &s) == 0)
  131.     return zname;
  132.     }
  133.  
  134.   *pferr = FALSE;
  135.   return NULL;
  136. }
  137.  
  138. /* Expand a local filename for uuxqt.  This is special because uuxqt
  139.    only wants to expand filenames that start with ~ (it does not want
  140.    to prepend the current directory to other names) and if the ~ is
  141.    double, it is turned into a single ~.  This returns NULL to
  142.    indicate that no change was required; it has no way to return
  143.    error.  */
  144.  
  145. char *
  146. zsysdep_xqt_local_file (qsys, zfile)
  147.      const struct uuconf_system *qsys;
  148.      const char *zfile;
  149. {
  150.   if (*zfile != '~')
  151.     return NULL;
  152.   if (zfile[1] == '~')
  153.     {
  154.       size_t clen;
  155.       char *zret;
  156.  
  157.       clen = strlen (zfile);
  158.       zret = zbufalc (clen);
  159.       memcpy (zret, zfile + 1, clen);
  160.       return zret;
  161.     }
  162.   return zsysdep_local_file (zfile, qsys->uuconf_zpubdir);
  163. }
  164.  
  165. #if ! ALLOW_FILENAME_ARGUMENTS
  166.  
  167. /* Check to see whether an argument specifies a file name; if it does,
  168.    make sure that the file may legally be sent and/or received.  For
  169.    Unix, we do not permit any occurrence of "/../" in the name, nor
  170.    may it start with "../".  Otherwise, if it starts with "/" we check
  171.    against the list of permitted files.  */
  172.  
  173. boolean
  174. fsysdep_xqt_check_file (qsys, zfile)
  175.      const struct uuconf_system *qsys;
  176.      const char *zfile;
  177. {
  178.   size_t clen;
  179.  
  180.   clen = strlen (zfile);
  181.   if ((clen == sizeof "../" - 1
  182.        && strcmp (zfile, "../") == 0)
  183.       || (clen >= sizeof "/.." - 1
  184.       && strcmp (zfile + clen - (sizeof "/.." - 1), "/..") == 0)
  185.       || strstr (zfile, "/../") != NULL
  186.       || (*zfile == '/'
  187.       && (! fin_directory_list (zfile, qsys->uuconf_pzremote_send,
  188.                     qsys->uuconf_zpubdir, TRUE, FALSE,
  189.                     (const char *) NULL)
  190.           || ! fin_directory_list (zfile, qsys->uuconf_pzremote_receive,
  191.                        qsys->uuconf_zpubdir, TRUE, FALSE,
  192.                        (const char *) NULL))))
  193.     {
  194.       ulog (LOG_ERROR, "Not permitted to refer to file \"%s\"", zfile);
  195.       return FALSE;
  196.     }
  197.  
  198.   return TRUE;
  199. }
  200.  
  201. #endif /* ! ALLOW_FILENAME_ARGUMENTS */
  202.  
  203. /* Invoke the command specified by an execute file.  */
  204.  
  205. /*ARGSUSED*/
  206. boolean
  207. fsysdep_execute (qsys, zuser, pazargs, zfullcmd, zinput, zoutput,
  208.          fshell, iseq, pzerror, pftemp)
  209.      const struct uuconf_system *qsys;
  210.      const char *zuser;
  211.      const char **pazargs;
  212.      const char *zfullcmd;
  213.      const char *zinput;
  214.      const char *zoutput;
  215.      boolean fshell;
  216.      int iseq;
  217.      char **pzerror;
  218.      boolean *pftemp;
  219. {
  220.   int aidescs[3];
  221.   boolean ferr;
  222.   pid_t ipid;
  223.   int ierr;
  224.   char abxqtdir[sizeof XQTDIR + 4];
  225.   const char *zxqtdir;
  226.   int istat;
  227.   char *zpath;
  228. #if ALLOW_SH_EXECUTION
  229.   const char *azshargs[4];
  230. #endif
  231.  
  232.   *pzerror = NULL;
  233.   *pftemp = FALSE;
  234.  
  235.   aidescs[0] = SPAWN_NULL;
  236.   aidescs[1] = SPAWN_NULL;
  237.   aidescs[2] = SPAWN_NULL;
  238.  
  239.   ferr = FALSE;
  240.  
  241.   if (zinput != NULL)
  242.     {
  243.       aidescs[0] = open ((char *) zinput, O_RDONLY | O_NOCTTY, 0);
  244.       if (aidescs[0] < 0)
  245.     {
  246.       ulog (LOG_ERROR, "open (%s): %s", zinput, strerror (errno));
  247.       ferr = TRUE;
  248.     }
  249.       else if (fcntl (aidescs[0], F_SETFD,
  250.               fcntl (aidescs[0], F_GETFD, 0) | FD_CLOEXEC) < 0)
  251.     {
  252.       ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
  253.       ferr = TRUE;
  254.     }    
  255.     }
  256.   
  257.   if (! ferr && zoutput != NULL)
  258.     {
  259.       aidescs[1] = creat ((char *) zoutput, IPRIVATE_FILE_MODE);
  260.       if (aidescs[1] < 0)
  261.     {
  262.       ulog (LOG_ERROR, "creat (%s): %s", zoutput, strerror (errno));
  263.       *pftemp = TRUE;
  264.       ferr = TRUE;
  265.     }
  266.       else if (fcntl (aidescs[1], F_SETFD,
  267.               fcntl (aidescs[1], F_GETFD, 0) | FD_CLOEXEC) < 0)
  268.     {
  269.       ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
  270.       ferr = TRUE;
  271.     }    
  272.     }
  273.  
  274.   if (! ferr)
  275.     {
  276.       *pzerror = zstemp_file (qsys);
  277.       aidescs[2] = creat (*pzerror, IPRIVATE_FILE_MODE);
  278.       if (aidescs[2] < 0)
  279.     {
  280.       if (errno == ENOENT)
  281.         {
  282.           if (! fsysdep_make_dirs (*pzerror, FALSE))
  283.         {
  284.           *pftemp = TRUE;
  285.           ferr = TRUE;
  286.         }
  287.           else
  288.         aidescs[2] = creat (*pzerror, IPRIVATE_FILE_MODE);
  289.         }
  290.       if (! ferr && aidescs[2] < 0)
  291.         {
  292.           ulog (LOG_ERROR, "creat (%s): %s", *pzerror, strerror (errno));
  293.           *pftemp = TRUE;
  294.           ferr = TRUE;
  295.         }
  296.     }
  297.       if (! ferr
  298.       && fcntl (aidescs[2], F_SETFD,
  299.             fcntl (aidescs[2], F_GETFD, 0) | FD_CLOEXEC) < 0)
  300.     {
  301.       ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
  302.       ferr = TRUE;
  303.     }    
  304.     }
  305.  
  306.   if (iseq == 0)
  307.     zxqtdir = XQTDIR;
  308.   else
  309.     {
  310.       sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
  311.       zxqtdir = abxqtdir;
  312.     }
  313.  
  314.   if (ferr)
  315.     {
  316.       if (aidescs[0] != SPAWN_NULL)
  317.     (void) close (aidescs[0]);
  318.       if (aidescs[1] != SPAWN_NULL)
  319.     (void) close (aidescs[1]);
  320.       if (aidescs[2] != SPAWN_NULL)
  321.     (void) close (aidescs[2]);
  322.       ubuffree (*pzerror);
  323.       return FALSE;
  324.     }
  325.  
  326. #if ALLOW_SH_EXECUTION
  327.   if (fshell)
  328.     {
  329.       azshargs[0] = "/bin/sh";
  330.       azshargs[1] = "-c";
  331.       azshargs[2] = zfullcmd;
  332.       azshargs[3] = NULL;
  333.       pazargs = azshargs;
  334.     }
  335. #else
  336.   fshell = FALSE;
  337. #endif
  338.  
  339.   if (qsys->uuconf_pzpath == NULL)
  340.     zpath = NULL;
  341.   else
  342.     {
  343.       size_t c;
  344.       char **pz;
  345.  
  346.       c = 0;
  347.       for (pz = qsys->uuconf_pzpath; *pz != NULL; pz++)
  348.     c += strlen (*pz) + 1;
  349.       zpath = zbufalc (c);
  350.       *zpath = '\0';
  351.       for (pz = qsys->uuconf_pzpath; *pz != NULL; pz++)
  352.     {
  353.       strcat (zpath, *pz);
  354.       if (pz[1] != NULL)
  355.         strcat (zpath, ":");
  356.     }
  357.     }
  358.  
  359.   /* Pass zchdir as zxqtdir, fnosigs as TRUE, fshell as TRUE if we
  360.      aren't already using the shell.  */
  361.   ipid = ixsspawn (pazargs, aidescs, FALSE, FALSE, zxqtdir, TRUE,
  362.            ! fshell, zpath, qsys->uuconf_zname, zuser);
  363.  
  364.   ierr = errno;
  365.  
  366.   ubuffree (zpath);
  367.  
  368.   if (aidescs[0] != SPAWN_NULL)
  369.     (void) close (aidescs[0]);
  370.   if (aidescs[1] != SPAWN_NULL)
  371.     (void) close (aidescs[1]);
  372.   if (aidescs[2] != SPAWN_NULL)
  373.     (void) close (aidescs[2]);
  374.  
  375.   if (ipid < 0)
  376.     {
  377.       ulog (LOG_ERROR, "ixsspawn: %s", strerror (ierr));
  378.       *pftemp = TRUE;
  379.       return FALSE;
  380.     }
  381.  
  382.   istat = ixswait ((unsigned long) ipid, "Execution");
  383.  
  384.   if (istat == EX_TEMPFAIL)
  385.     *pftemp = TRUE;
  386.  
  387.   return istat == 0;
  388. }
  389.  
  390. /* Lock a uuxqt process.  */
  391.  
  392. int
  393. ixsysdep_lock_uuxqt (zcmd, cmaxuuxqts)
  394.      const char *zcmd;
  395.      int cmaxuuxqts;
  396. {
  397.   char ab[sizeof "LCK.XQT.9999"];
  398.   int i;
  399.  
  400.   if (cmaxuuxqts <= 0 || cmaxuuxqts >= 10000)
  401.     cmaxuuxqts = 9999;
  402.   for (i = 0; i < cmaxuuxqts; i++)
  403.     {
  404.       sprintf (ab, "LCK.XQT.%d", i);
  405.       if (fsdo_lock (ab, TRUE, (boolean *) NULL))
  406.     break;
  407.     }
  408.   if (i >= cmaxuuxqts)
  409.     return -1;
  410.  
  411.   if (zcmd != NULL)
  412.     {
  413.       char abcmd[sizeof "LXQ.123456789"];
  414.  
  415.       sprintf (abcmd, "LXQ.%.9s", zcmd);
  416.       abcmd[strcspn (abcmd, " \t/")] = '\0';
  417.       if (! fsdo_lock (abcmd, TRUE, (boolean *) NULL))
  418.     {
  419.       (void) fsdo_unlock (ab, TRUE);
  420.       return -1;
  421.     }
  422.     }
  423.  
  424.   return i;
  425. }
  426.  
  427. /* Unlock a uuxqt process.  */
  428.  
  429. boolean
  430. fsysdep_unlock_uuxqt (iseq, zcmd, cmaxuuxqts)
  431.      int iseq;
  432.      const char *zcmd;
  433.      int cmaxuuxqts;
  434. {
  435.   char ab[sizeof "LCK.XQT.9999"];
  436.   boolean fret;
  437.  
  438.   fret = TRUE;
  439.  
  440.   sprintf (ab, "LCK.XQT.%d", iseq);
  441.   if (! fsdo_unlock (ab, TRUE))
  442.     fret = FALSE;
  443.  
  444.   if (zcmd != NULL)
  445.     {
  446.       char abcmd[sizeof "LXQ.123456789"];
  447.  
  448.       sprintf (abcmd, "LXQ.%.9s", zcmd);
  449.       abcmd[strcspn (abcmd, " \t/")] = '\0';
  450.       if (! fsdo_unlock (abcmd, TRUE))
  451.     fret = FALSE;
  452.     }
  453.  
  454.   return fret;
  455. }
  456.  
  457. /* See whether a particular uuxqt command is locked (this depends on
  458.    the implementation of fsdo_lock).  */
  459.  
  460. boolean
  461. fsysdep_uuxqt_locked (zcmd)
  462.      const char *zcmd;
  463. {
  464.   char ab[sizeof "LXQ.123456789"];
  465.   struct stat s;
  466.  
  467.   sprintf (ab, "LXQ.%.9s", zcmd);
  468.   return stat (ab, &s) == 0;
  469. }
  470.  
  471. /* Lock a particular execute file.  */
  472.  
  473. boolean
  474. fsysdep_lock_uuxqt_file (zfile)
  475.      const char *zfile;
  476. {
  477.   char *zcopy, *z;
  478.   boolean fret;
  479.  
  480.   zcopy = zbufcpy (zfile);
  481.  
  482.   z = strrchr (zcopy, '/');
  483.   if (z == NULL)
  484.     *zcopy = 'L';
  485.   else
  486.     *(z + 1) = 'L';
  487.  
  488.   fret = fsdo_lock (zcopy, TRUE, (boolean *) NULL);
  489.   ubuffree (zcopy);
  490.   return fret;
  491. }
  492.  
  493. /* Unlock a particular execute file.  */
  494.  
  495. boolean
  496. fsysdep_unlock_uuxqt_file (zfile)
  497.      const char *zfile;
  498. {
  499.   char *zcopy, *z;
  500.   boolean fret;
  501.  
  502.   zcopy = zbufcpy (zfile);
  503.  
  504.   z = strrchr (zcopy, '/');
  505.   if (z == NULL)
  506.     *zcopy = 'L';
  507.   else
  508.     *(z + 1) = 'L';
  509.  
  510.   fret = fsdo_unlock (zcopy, TRUE);
  511.   ubuffree (zcopy);
  512.   return fret;
  513. }
  514.  
  515. /* Lock the execute directory.  Since we use a different directory
  516.    depending on which LCK.XQT.dddd file we got, there is actually no
  517.    need to create a lock file.  We do make sure that the directory
  518.    exists, though.  */
  519.  
  520. boolean
  521. fsysdep_lock_uuxqt_dir (iseq)
  522.      int iseq;
  523. {
  524.   const char *zxqtdir;
  525.   char abxqtdir[sizeof XQTDIR + 4];
  526.  
  527.   if (iseq == 0)
  528.     zxqtdir = XQTDIR;
  529.   else
  530.     {
  531.       sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
  532.       zxqtdir = abxqtdir;
  533.     }
  534.  
  535.   if (mkdir (zxqtdir, S_IRWXU) < 0
  536.       && errno != EEXIST)
  537.     {
  538.       ulog (LOG_ERROR, "mkdir (%s): %s", zxqtdir, strerror (errno));
  539.       return FALSE;
  540.     }
  541.  
  542.   return TRUE;
  543. }
  544.  
  545. /* Unlock the execute directory and clear it out.  The lock is
  546.    actually the LCK.XQT.dddd file, so we don't unlock it, but we do
  547.    remove all the files.  */
  548.  
  549. boolean
  550. fsysdep_unlock_uuxqt_dir (iseq)
  551.      int iseq;
  552. {
  553.   const char *zxqtdir;
  554.   char abxqtdir[sizeof XQTDIR + 4];
  555.   DIR *qdir;
  556.  
  557.   if (iseq == 0)
  558.     zxqtdir = XQTDIR;
  559.   else
  560.     {
  561.       sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
  562.       zxqtdir = abxqtdir;
  563.     }
  564.  
  565.   qdir = opendir ((char *) zxqtdir);
  566.   if (qdir != NULL)
  567.     {
  568.       struct dirent *qentry;
  569.  
  570.       while ((qentry = readdir (qdir)) != NULL)
  571.     {
  572.       char *z;
  573.  
  574.       if (strcmp (qentry->d_name, ".") == 0
  575.           || strcmp (qentry->d_name, "..") == 0)
  576.         continue;
  577.       z = zsysdep_in_dir (zxqtdir, qentry->d_name);
  578.       if (remove (z) < 0)
  579.         {
  580.           int ierr;
  581.  
  582.           ierr = errno;
  583.           if (! fsysdep_directory (z))
  584.         ulog (LOG_ERROR, "remove (%s): %s", z,
  585.               strerror (ierr));
  586.           else
  587.         (void) fsysdep_rmdir (z);
  588.         }
  589.       ubuffree (z);
  590.     }
  591.  
  592.       closedir (qdir);
  593.     }
  594.  
  595.   return TRUE;
  596. }
  597.  
  598. /* Move files into the execution directory.  */
  599.  
  600. boolean
  601. fsysdep_move_uuxqt_files (cfiles, pzfrom, pzto, fto, iseq, pzinput)
  602.      int cfiles;
  603.      const char *const *pzfrom;
  604.      const char *const *pzto;
  605.      boolean fto;
  606.      int iseq;
  607.      char **pzinput;
  608. {
  609.   char *zinput;
  610.   const char *zxqtdir;
  611.   char abxqtdir[sizeof XQTDIR + 4];
  612.   int i;
  613.  
  614.   if (pzinput == NULL)
  615.     zinput = NULL;
  616.   else
  617.     zinput = *pzinput;
  618.  
  619.   if (iseq == 0)
  620.     zxqtdir = XQTDIR;
  621.   else
  622.     {
  623.       sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
  624.       zxqtdir = abxqtdir;
  625.     }
  626.  
  627.   for (i = 0; i < cfiles; i++)
  628.     {
  629.       const char *zfrom, *zto;
  630.       char *zfree;
  631.  
  632.       if (pzto[i] == NULL)
  633.     continue;
  634.  
  635.       zfree = zsysdep_in_dir (zxqtdir, pzto[i]);
  636.  
  637.       zfrom = pzfrom[i];
  638.       zto = zfree;
  639.  
  640.       if (zinput != NULL && strcmp (zinput, zfrom) == 0)
  641.     {
  642.       *pzinput = zbufcpy (zto);
  643.       zinput = NULL;
  644.     }
  645.  
  646.       if (! fto)
  647.     {
  648.       const char *ztemp;
  649.       
  650.       ztemp = zfrom;
  651.       zfrom = zto;
  652.       zto = ztemp;
  653.       (void) chmod (zfrom, IPRIVATE_FILE_MODE);
  654.     }
  655.  
  656.       if (rename (zfrom, zto) < 0)
  657.     {
  658. #if HAVE_RENAME
  659.       /* On some systems the system call rename seems to fail for
  660.          arbitrary reasons.  To get around this, we always try to
  661.          copy the file by hand if the rename failed.  */
  662.       errno = EXDEV;
  663. #endif
  664.  
  665.       if (errno != EXDEV)
  666.         {
  667.           ulog (LOG_ERROR, "rename (%s, %s): %s", zfrom, zto,
  668.             strerror (errno));
  669.           ubuffree (zfree);
  670.           break;
  671.         }
  672.  
  673.       if (! fcopy_file (zfrom, zto, FALSE, FALSE))
  674.         {
  675.           ubuffree (zfree);
  676.           break;
  677.         }
  678.       if (remove (zfrom) < 0)
  679.         ulog (LOG_ERROR, "remove (%s): %s", zfrom,
  680.           strerror (errno));
  681.     }
  682.  
  683.       if (fto)
  684.     (void) chmod (zto, IPUBLIC_FILE_MODE);
  685.  
  686.       ubuffree (zfree);
  687.     }
  688.  
  689.   if (i < cfiles)
  690.     {
  691.       if (fto)
  692.     (void) fsysdep_move_uuxqt_files (i, pzfrom, pzto, FALSE, iseq,
  693.                      (char **) NULL);
  694.       return FALSE;
  695.     }
  696.  
  697.   return TRUE;
  698. }
  699.