Example #1
0
/*
 * Called here to do a special operation on a variable
 *   op_ptr  points to the special operation code (not EOS terminated)
 *   arg_ptr points to argument to special op code
 *   val_ptr points to the value string
 *   out_ptr points to string to be returned
 */
static var_rc_t operate_var(var_t *var,
                            void *my_ctx,
                            const char *op_ptr,
                            int op_len,
                            const char *arg_ptr,
                            int arg_len,
                            const char *val_ptr,
                            int val_len,
                            char **out_ptr,
                            int *out_len,
                            int *out_size)
{
    COUNTERRES *counter;
    POOL_MEM buf(PM_NAME);
    var_rc_t status = VAR_ERR_UNDEFINED_OPERATION;

    Dmsg0(100, "Enter operate_var\n");
    if (!val_ptr) {
        *out_size = 0;
        return status;
    }

    if (op_len == 3 && bstrncmp(op_ptr, "inc", 3)) {
        buf.check_size(val_len + 1);
        pm_memcpy(buf, arg_ptr, val_len);
        (buf.c_str())[val_len] = 0;
        Dmsg1(100, "Arg=%s\n", buf.c_str());

        pm_memcpy(buf, val_ptr, val_len);
        (buf.c_str())[val_len] = 0;
        Dmsg1(100, "Val=%s\n", buf.c_str());

        LockRes();
        for (counter = NULL; (counter = (COUNTERRES *)GetNextRes(R_COUNTER, (RES *)counter)); ) {
            if (bstrcmp(counter->name(), buf.c_str())) {
                Dmsg2(100, "counter=%s val=%s\n", counter->name(), buf.c_str());
                break;
            }
        }
        UnlockRes();
        return status;
    }
    *out_size = 0;

    return status;
}
Example #2
0
/**
 * Save OSX specific resource forks and finder info.
 */
static inline bool save_rsrc_and_finder(b_save_ctx &bsctx)
{
   char flags[FOPTS_BYTES];
   int rsrc_stream;
   BSOCK *sd = bsctx.jcr->store_bsock;
   bool retval = false;

   if (bsctx.ff_pkt->hfsinfo.rsrclength > 0) {
      if (bopen_rsrc(&bsctx.ff_pkt->bfd, bsctx.ff_pkt->fname, O_RDONLY | O_BINARY, 0) < 0) {
         bsctx.ff_pkt->ff_errno = errno;
         berrno be;
         Jmsg(bsctx.jcr, M_NOTSAVED, -1,
              _("     Cannot open resource fork for \"%s\": ERR=%s.\n"),
              bsctx.ff_pkt->fname, be.bstrerror());
         bsctx.jcr->JobErrors++;
         if (is_bopen(&bsctx.ff_pkt->bfd)) {
            bclose(&bsctx.ff_pkt->bfd);
         }
      } else {
         int status;

         memcpy(flags, bsctx.ff_pkt->flags, sizeof(flags));
         clear_bit(FO_COMPRESS, bsctx.ff_pkt->flags);
         clear_bit(FO_SPARSE, bsctx.ff_pkt->flags);
         clear_bit(FO_OFFSETS, bsctx.ff_pkt->flags);
         rsrc_stream = bit_is_set(FO_ENCRYPT, flags) ? STREAM_ENCRYPTED_MACOS_FORK_DATA :
                                                       STREAM_MACOS_FORK_DATA;

         status = send_data(bsctx.jcr, rsrc_stream, bsctx.ff_pkt,
                            bsctx.digest, bsctx.signing_digest);

         memcpy(bsctx.ff_pkt->flags, flags, sizeof(flags));
         bclose(&bsctx.ff_pkt->bfd);
         if (!status) {
            goto bail_out;
         }
      }
   }

   Dmsg1(300, "Saving Finder Info for \"%s\"\n", bsctx.ff_pkt->fname);
   sd->fsend("%ld %d 0", bsctx.jcr->JobFiles, STREAM_HFSPLUS_ATTRIBUTES);
   Dmsg1(300, "filed>stored:header %s", sd->msg);
   pm_memcpy(sd->msg, bsctx.ff_pkt->hfsinfo.fndrinfo, 32);
   sd->msglen = 32;
   if (bsctx.digest) {
      crypto_digest_update(bsctx.digest, (uint8_t *)sd->msg, sd->msglen);
   }
   if (bsctx.signing_digest) {
      crypto_digest_update(bsctx.signing_digest, (uint8_t *)sd->msg, sd->msglen);
   }
   sd->send();
   sd->signal(BNET_EOD);

   retval = true;

bail_out:
   return retval;
}
Example #3
0
/*
 * Called here from "core" expand code to look up a variable
 */
static var_rc_t lookup_var(var_t *ctx,
                           void *my_ctx,
                           const char *var_ptr,
                           int var_len,
                           int var_inc,
                           int var_index,
                           const char **val_ptr,
                           int *val_len,
                           int *val_size)
{
    POOL_MEM buf(PM_NAME);
    char *val, *p, *v;
    var_rc_t status;
    int count;

    /*
     * Note, if val_size > 0 and val_ptr!=NULL, the core code will free() it
     */
    if ((status = lookup_built_in_var(ctx, my_ctx, var_ptr, var_len, var_index,
                                      val_ptr, val_len, val_size)) == VAR_OK) {
        return VAR_OK;
    }

    if ((status = lookup_counter_var(ctx, my_ctx, var_ptr, var_len, var_inc, var_index,
                                     val_ptr, val_len, val_size)) == VAR_OK) {
        return VAR_OK;
    }

    /*
     * Look in environment
     */
    buf.check_size(var_len + 1);
    pm_memcpy(buf, var_ptr, var_len);
    (buf.c_str())[var_len] = 0;
    Dmsg1(100, "Var=%s\n", buf.c_str());

    if ((val = getenv(buf.c_str())) == NULL) {
        return VAR_ERR_UNDEFINED_VARIABLE;
    }

    /*
     * He wants to index the "array"
     */
    count = 1;

    /*
     * Find the size of the "array" each element is separated by a |
     */
    for (p = val; *p; p++) {
        if (*p == '|') {
            count++;
        }
    }

    Dmsg3(100, "For %s, reqest index=%d have=%d\n", buf.c_str(), var_index, count);

    /*
     * -1 => return size of array
     */
    if (var_index == -1) {
        int len;
        if (count == 1) {               /* if not array */
            len = strlen(val);           /* return length of string */
        } else {
            len = count;                 /* else return # array items */
        }
        *val_len = Mmsg(buf, "%d", len);
        *val_ptr = bstrdup(buf.c_str());
        *val_size = 0;                  /* don't try to free val_ptr */
        return VAR_OK;
    }


    if (var_index < -1 || var_index > --count) {
//    return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
        return VAR_ERR_UNDEFINED_VARIABLE;
    }

    /*
     * Now find the particular item (var_index) he wants
     */
    count = 0;
    for (p = val; *p; ) {
        if (*p == '|') {
            if (count < var_index) {
                val = ++p;
                count++;
                continue;
            }
            break;
        }
        p++;
    }

    buf.check_size(p - val);
    Dmsg2(100, "val=%s len=%d\n", val, p - val);

    /*
     * Make a copy of item, and pass it back
     */
    v = (char *)malloc(p-val+1);
    memcpy(v, val, p-val);
    v[p-val] = 0;
    *val_ptr = v;
    *val_len = p-val;
    *val_size = p-val+1;
    Dmsg1(100, "v=%s\n", v);

    return VAR_OK;
}
Example #4
0
/*
 * Search counter variables
 */
static var_rc_t lookup_counter_var(var_t *ctx,
                                   void *my_ctx,
                                   const char *var_ptr,
                                   int var_len,
                                   int var_inc,
                                   int var_index,
                                   const char **val_ptr,
                                   int *val_len,
                                   int *val_size)
{
    COUNTERRES *counter;
    POOL_MEM buf(PM_NAME);
    var_rc_t status = VAR_ERR_UNDEFINED_VARIABLE;

    buf.check_size(var_len + 1);
    pm_memcpy(buf, var_ptr, var_len);
    (buf.c_str())[var_len] = 0;

    LockRes();
    for (counter = NULL; (counter = (COUNTERRES *)GetNextRes(R_COUNTER, (RES *)counter)); ) {
        if (bstrcmp(counter->name(), buf.c_str())) {
            Dmsg2(100, "Counter=%s val=%d\n", buf.c_str(), counter->CurrentValue);
            /*
             * -1 => return size of array
             */
            if (var_index == -1) {
                Mmsg(buf, "%d", counter->CurrentValue);
                *val_len = Mmsg(buf, "%d", strlen(buf.c_str()));
                *val_ptr = bstrdup(buf.c_str());
                *val_size = 0;                  /* don't try to free val_ptr */
                return VAR_OK;
            } else {
                Mmsg(buf, "%d", counter->CurrentValue);
                *val_ptr = bstrdup(buf.c_str());
                *val_len = strlen(buf.c_str());
                *val_size = *val_len + 1;
            }
            if (var_inc) {               /* increment the variable? */
                if (counter->CurrentValue == counter->MaxValue) {
                    counter->CurrentValue = counter->MinValue;
                } else {
                    counter->CurrentValue++;
                }
                if (counter->Catalog) {   /* update catalog if need be */
                    COUNTER_DBR cr;
                    JCR *jcr = (JCR *)my_ctx;
                    memset(&cr, 0, sizeof(cr));
                    bstrncpy(cr.Counter, counter->name(), sizeof(cr.Counter));
                    cr.MinValue = counter->MinValue;
                    cr.MaxValue = counter->MaxValue;
                    cr.CurrentValue = counter->CurrentValue;
                    Dmsg1(100, "New value=%d\n", cr.CurrentValue);
                    if (counter->WrapCounter) {
                        bstrncpy(cr.WrapCounter, counter->WrapCounter->name(), sizeof(cr.WrapCounter));
                    } else {
                        cr.WrapCounter[0] = 0;
                    }
                    if (!db_update_counter_record(jcr, jcr->db, &cr)) {
                        Jmsg(jcr, M_ERROR, 0, _("Count not update counter %s: ERR=%s\n"),
                             counter->name(), db_strerror(jcr->db));
                    }
                }
            }
            status = VAR_OK;
            break;
        }
    }
    UnlockRes();

    return status;
}
Example #5
0
/*
 * Called here by find() for each file included.
 *   This is a callback. The original is find_files() above.
 *
 *  Send the file and its data to the Storage daemon.
 *
 *  Returns: 1 if OK
 *           0 if error
 *          -1 to ignore file/directory (not used here)
 */
int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
{
   bool do_read = false;
   int stat, data_stream; 
   int rtnstat = 0;
   DIGEST *digest = NULL;
   DIGEST *signing_digest = NULL;
   int digest_stream = STREAM_NONE;
   SIGNATURE *sig = NULL;
   bool has_file_data = false;
   // TODO landonf: Allow the user to specify the digest algorithm
#ifdef HAVE_SHA2
   crypto_digest_t signing_algorithm = CRYPTO_DIGEST_SHA256;
#else
   crypto_digest_t signing_algorithm = CRYPTO_DIGEST_SHA1;
#endif
   BSOCK *sd = jcr->store_bsock;

   if (job_canceled(jcr)) {
      return 0;
   }

   jcr->num_files_examined++;         /* bump total file count */

   switch (ff_pkt->type) {
   case FT_LNKSAVED:                  /* Hard linked, file already saved */
      Dmsg2(130, "FT_LNKSAVED hard link: %s => %s\n", ff_pkt->fname, ff_pkt->link);
      break;
   case FT_REGE:
      Dmsg1(130, "FT_REGE saving: %s\n", ff_pkt->fname);
      has_file_data = true;
      break;
   case FT_REG:
      Dmsg1(130, "FT_REG saving: %s\n", ff_pkt->fname);
      has_file_data = true;
      break;
   case FT_LNK:
      Dmsg2(130, "FT_LNK saving: %s -> %s\n", ff_pkt->fname, ff_pkt->link);
      break;
   case FT_DIRBEGIN:
      jcr->num_files_examined--;      /* correct file count */
      return 1;                       /* not used */
   case FT_NORECURSE:
      Jmsg(jcr, M_INFO, 1, _("     Recursion turned off. Will not descend from %s into %s\n"),
           ff_pkt->top_fname, ff_pkt->fname);
      ff_pkt->type = FT_DIREND;       /* Backup only the directory entry */
      break;
   case FT_NOFSCHG:
      /* Suppress message for /dev filesystems */
      if (!is_in_fileset(ff_pkt)) {
         Jmsg(jcr, M_INFO, 1, _("     %s is a different filesystem. Will not descend from %s into %s\n"),
              ff_pkt->fname, ff_pkt->top_fname, ff_pkt->fname);
      }
      ff_pkt->type = FT_DIREND;       /* Backup only the directory entry */
      break;
   case FT_INVALIDFS:
      Jmsg(jcr, M_INFO, 1, _("     Disallowed filesystem. Will not descend from %s into %s\n"),
           ff_pkt->top_fname, ff_pkt->fname);
      ff_pkt->type = FT_DIREND;       /* Backup only the directory entry */
      break;
   case FT_INVALIDDT:
      Jmsg(jcr, M_INFO, 1, _("     Disallowed drive type. Will not descend into %s\n"),
           ff_pkt->fname);
      break;
   case FT_REPARSE:
   case FT_DIREND:
      Dmsg1(130, "FT_DIREND: %s\n", ff_pkt->link);
      break;
   case FT_SPEC:
      Dmsg1(130, "FT_SPEC saving: %s\n", ff_pkt->fname);
      if (S_ISSOCK(ff_pkt->statp.st_mode)) {
        Jmsg(jcr, M_SKIPPED, 1, _("     Socket file skipped: %s\n"), ff_pkt->fname);
        return 1;
      }
      break;
   case FT_RAW:
      Dmsg1(130, "FT_RAW saving: %s\n", ff_pkt->fname);
      has_file_data = true;
      break;
   case FT_FIFO:
      Dmsg1(130, "FT_FIFO saving: %s\n", ff_pkt->fname);
      break;
   case FT_NOACCESS: {
      berrno be;
      Jmsg(jcr, M_NOTSAVED, 0, _("     Could not access \"%s\": ERR=%s\n"), ff_pkt->fname,
         be.bstrerror(ff_pkt->ff_errno));
      jcr->JobErrors++;
      return 1;
   }
   case FT_NOFOLLOW: {
      berrno be;
      Jmsg(jcr, M_NOTSAVED, 0, _("     Could not follow link \"%s\": ERR=%s\n"), 
           ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno));
      jcr->JobErrors++;
      return 1;
   }
   case FT_NOSTAT: {
      berrno be;
      Jmsg(jcr, M_NOTSAVED, 0, _("     Could not stat \"%s\": ERR=%s\n"), ff_pkt->fname,
         be.bstrerror(ff_pkt->ff_errno));
      jcr->JobErrors++;
      return 1;
   }
   case FT_DIRNOCHG:
   case FT_NOCHG:
      Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"), ff_pkt->fname);
      return 1;
   case FT_ISARCH:
      Jmsg(jcr, M_NOTSAVED, 0, _("     Archive file not saved: %s\n"), ff_pkt->fname);
      return 1;
   case FT_NOOPEN: {
      berrno be;
      Jmsg(jcr, M_NOTSAVED, 0, _("     Could not open directory \"%s\": ERR=%s\n"), 
           ff_pkt->fname, be.bstrerror(ff_pkt->ff_errno));
      jcr->JobErrors++;
      return 1;
   }
   default:
      Jmsg(jcr, M_NOTSAVED, 0,  _("     Unknown file type %d; not saved: %s\n"), 
           ff_pkt->type, ff_pkt->fname);
      jcr->JobErrors++;
      return 1;
   }

   Dmsg1(130, "bfiled: sending %s to stored\n", ff_pkt->fname);

   /* Digests and encryption are only useful if there's file data */
   if (has_file_data) {
      /*
       * Setup for digest handling. If this fails, the digest will be set to NULL
       * and not used. Note, the digest (file hash) can be any one of the four
       * algorithms below.
       *
       * The signing digest is a single algorithm depending on
       * whether or not we have SHA2.              
       *   ****FIXME****  the signing algoritm should really be
       *   determined a different way!!!!!!  What happens if
       *   sha2 was available during backup but not restore?
       */
      if (ff_pkt->flags & FO_MD5) {
         digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
         digest_stream = STREAM_MD5_DIGEST;

      } else if (ff_pkt->flags & FO_SHA1) {
         digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
         digest_stream = STREAM_SHA1_DIGEST;

      } else if (ff_pkt->flags & FO_SHA256) {
         digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
         digest_stream = STREAM_SHA256_DIGEST;

      } else if (ff_pkt->flags & FO_SHA512) {
         digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
         digest_stream = STREAM_SHA512_DIGEST;
      }

      /* Did digest initialization fail? */
      if (digest_stream != STREAM_NONE && digest == NULL) {
         Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
            stream_to_ascii(digest_stream));
      }

      /*
       * Set up signature digest handling. If this fails, the signature digest will be set to
       * NULL and not used.
       */
      // TODO landonf: We should really only calculate the digest once, for both verification and signing.
      if (jcr->crypto.pki_sign) {
         signing_digest = crypto_digest_new(jcr, signing_algorithm);

         /* Full-stop if a failure occurred initializing the signature digest */
         if (signing_digest == NULL) {
            Jmsg(jcr, M_NOTSAVED, 0, _("%s signature digest initialization failed\n"),
               stream_to_ascii(signing_algorithm));
            jcr->JobErrors++;
            goto good_rtn;
         }
      }

      /* Enable encryption */
      if (jcr->crypto.pki_encrypt) {
         ff_pkt->flags |= FO_ENCRYPT;
      }
   }

   /* Initialize the file descriptor we use for data and other streams. */
   binit(&ff_pkt->bfd);
   if (ff_pkt->flags & FO_PORTABLE) {
      set_portable_backup(&ff_pkt->bfd); /* disable Win32 BackupRead() */
   }
   if (ff_pkt->cmd_plugin) {
      if (!set_cmd_plugin(&ff_pkt->bfd, jcr)) {
         goto bail_out;
      }
      send_plugin_name(jcr, sd, true);      /* signal start of plugin data */
   }

   /* Send attributes -- must be done after binit() */
   if (!encode_and_send_attributes(jcr, ff_pkt, data_stream)) {
      goto bail_out;
   }

   /* Set up the encryption context and send the session data to the SD */
   if (has_file_data && jcr->crypto.pki_encrypt) {
      if (!crypto_session_send(jcr, sd)) {
         goto bail_out;
      }
   }

   /*
    * Open any file with data that we intend to save, then save it.
    *
    * Note, if is_win32_backup, we must open the Directory so that
    * the BackupRead will save its permissions and ownership streams.
    */
   if (ff_pkt->type != FT_LNKSAVED && S_ISREG(ff_pkt->statp.st_mode)) {
#ifdef HAVE_WIN32
      do_read = !is_portable_backup(&ff_pkt->bfd) || ff_pkt->statp.st_size > 0;
#else
      do_read = ff_pkt->statp.st_size > 0;  
#endif
   } else if (ff_pkt->type == FT_RAW || ff_pkt->type == FT_FIFO ||
              ff_pkt->type == FT_REPARSE ||
         (!is_portable_backup(&ff_pkt->bfd) && ff_pkt->type == FT_DIREND)) {
      do_read = true;
   }
   if (ff_pkt->cmd_plugin) {
      do_read = true;
   }

   Dmsg1(400, "do_read=%d\n", do_read);
   if (do_read) {
      btimer_t *tid;

      if (ff_pkt->type == FT_FIFO) {
         tid = start_thread_timer(jcr, pthread_self(), 60);
      } else {
         tid = NULL;
      }
      int noatime = ff_pkt->flags & FO_NOATIME ? O_NOATIME : 0;
      ff_pkt->bfd.reparse_point = ff_pkt->type == FT_REPARSE;
      if (bopen(&ff_pkt->bfd, ff_pkt->fname, O_RDONLY | O_BINARY | noatime, 0) < 0) {
         ff_pkt->ff_errno = errno;
         berrno be;
         Jmsg(jcr, M_NOTSAVED, 0, _("     Cannot open \"%s\": ERR=%s.\n"), ff_pkt->fname,
              be.bstrerror());
         jcr->JobErrors++;
         if (tid) {
            stop_thread_timer(tid);
            tid = NULL;
         }
         goto good_rtn;
      }
      if (tid) {
         stop_thread_timer(tid);
         tid = NULL;
      }

      stat = send_data(jcr, data_stream, ff_pkt, digest, signing_digest);

      if (ff_pkt->flags & FO_CHKCHANGES) {
         has_file_changed(jcr, ff_pkt);
      }

      bclose(&ff_pkt->bfd);
      
      if (!stat) {
         goto bail_out;
      }
   }

#ifdef HAVE_DARWIN_OS
   /* Regular files can have resource forks and Finder Info */
   if (ff_pkt->type != FT_LNKSAVED && (S_ISREG(ff_pkt->statp.st_mode) &&
            ff_pkt->flags & FO_HFSPLUS)) {
      if (ff_pkt->hfsinfo.rsrclength > 0) {
         int flags;
         int rsrc_stream;
         if (!bopen_rsrc(&ff_pkt->bfd, ff_pkt->fname, O_RDONLY | O_BINARY, 0) < 0) {
            ff_pkt->ff_errno = errno;
            berrno be;
            Jmsg(jcr, M_NOTSAVED, -1, _("     Cannot open resource fork for \"%s\": ERR=%s.\n"), 
                 ff_pkt->fname, be.bstrerror());
            jcr->JobErrors++;
            if (is_bopen(&ff_pkt->bfd)) {
               bclose(&ff_pkt->bfd);
            }
            goto good_rtn;
         }
         flags = ff_pkt->flags;
         ff_pkt->flags &= ~(FO_GZIP|FO_SPARSE);
         if (flags & FO_ENCRYPT) {
            rsrc_stream = STREAM_ENCRYPTED_MACOS_FORK_DATA;
         } else {
            rsrc_stream = STREAM_MACOS_FORK_DATA;
         }
         stat = send_data(jcr, rsrc_stream, ff_pkt, digest, signing_digest);
         ff_pkt->flags = flags;
         bclose(&ff_pkt->bfd);
         if (!stat) {
            goto bail_out;
         }
      }

      Dmsg1(300, "Saving Finder Info for \"%s\"\n", ff_pkt->fname);
      sd->fsend("%ld %d 0", jcr->JobFiles, STREAM_HFSPLUS_ATTRIBUTES);
      Dmsg1(300, "bfiled>stored:header %s\n", sd->msg);
      pm_memcpy(sd->msg, ff_pkt->hfsinfo.fndrinfo, 32);
      sd->msglen = 32;
      if (digest) {
         crypto_digest_update(digest, (uint8_t *)sd->msg, sd->msglen);
      }
      if (signing_digest) {
         crypto_digest_update(signing_digest, (uint8_t *)sd->msg, sd->msglen);
      }
      sd->send();
      sd->signal(BNET_EOD);
   }
#endif

   /*
    * Save ACLs for anything not being a symlink and not being a plugin.
    */
   if (!ff_pkt->cmd_plugin) {
      if (ff_pkt->flags & FO_ACL && ff_pkt->type != FT_LNK) {
         if (!build_acl_streams(jcr, ff_pkt))
            goto bail_out;
      }
   }

   /*
    * Save Extended Attributes for all files not being a plugin.
    */
   if (!ff_pkt->cmd_plugin) {
      if (ff_pkt->flags & FO_XATTR) {
         if (!build_xattr_streams(jcr, ff_pkt))
            goto bail_out;
      }
   }

   /* Terminate the signing digest and send it to the Storage daemon */
   if (signing_digest) {
      uint32_t size = 0;

      if ((sig = crypto_sign_new(jcr)) == NULL) {
         Jmsg(jcr, M_FATAL, 0, _("Failed to allocate memory for crypto signature.\n"));
         goto bail_out;
      }

      if (!crypto_sign_add_signer(sig, signing_digest, jcr->crypto.pki_keypair)) {
         Jmsg(jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n"));
         goto bail_out;
      }

      /* Get signature size */
      if (!crypto_sign_encode(sig, NULL, &size)) {
         Jmsg(jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n"));
         goto bail_out;
      }

      /* Grow the bsock buffer to fit our message if necessary */
      if (sizeof_pool_memory(sd->msg) < (int32_t)size) {
         sd->msg = realloc_pool_memory(sd->msg, size);
      }

      /* Send our header */
      sd->fsend("%ld %ld 0", jcr->JobFiles, STREAM_SIGNED_DIGEST);
      Dmsg1(300, "bfiled>stored:header %s\n", sd->msg);

      /* Encode signature data */
      if (!crypto_sign_encode(sig, (uint8_t *)sd->msg, &size)) {
         Jmsg(jcr, M_FATAL, 0, _("An error occurred while signing the stream.\n"));
         goto bail_out;
      }

      sd->msglen = size;
      sd->send();
      sd->signal(BNET_EOD);              /* end of checksum */
   }

   /* Terminate any digest and send it to Storage daemon */
   if (digest) {
      uint32_t size;

      sd->fsend("%ld %d 0", jcr->JobFiles, digest_stream);
      Dmsg1(300, "bfiled>stored:header %s\n", sd->msg);

      size = CRYPTO_DIGEST_MAX_SIZE;

      /* Grow the bsock buffer to fit our message if necessary */
      if (sizeof_pool_memory(sd->msg) < (int32_t)size) {
         sd->msg = realloc_pool_memory(sd->msg, size);
      }

      if (!crypto_digest_finalize(digest, (uint8_t *)sd->msg, &size)) {
         Jmsg(jcr, M_FATAL, 0, _("An error occurred finalizing signing the stream.\n"));
         goto bail_out;
      }

      sd->msglen = size;
      sd->send();
      sd->signal(BNET_EOD);              /* end of checksum */
   }
   if (ff_pkt->cmd_plugin) {
      send_plugin_name(jcr, sd, false); /* signal end of plugin data */
   }

good_rtn:
   rtnstat = 1;                       /* good return */

bail_out:
   if (digest) {
      crypto_digest_free(digest);
   }
   if (signing_digest) {
      crypto_digest_free(signing_digest);
   }
   if (sig) {
      crypto_sign_free(sig);        
   }
   return rtnstat;
}