Пример #1
0
static void
followFileClose(
  follow_t *    fp
  )
{
  if (fp->filep != NULL)
  {
    fclose(fp->filep);
    fp->filep = NULL;
    if (fp->type == type_TCP)
    {
      data_out_file = NULL;
    }
  }

  if (fp->filenamep != NULL)
  {
    ws_unlink(fp->filenamep);
    g_free(fp->filenamep);
    fp->filenamep = NULL;
  }
}
Пример #2
0
/*
 * create the next filename and open a new binary file with that name
 */
static int ringbuf_open_file(rb_file *rfile, int *err)
{
  char    filenum[5+1];
  char    timestr[14+1];
  time_t  current_time;

  if (rfile->name != NULL) {
    if (rb_data.unlimited == FALSE) {
      /* remove old file (if any, so ignore error) */
      ws_unlink(rfile->name);
    }
    g_free(rfile->name);
  }

#ifdef _WIN32
  _tzset();
#endif
  current_time = time(NULL);

  g_snprintf(filenum, sizeof(filenum), "%05u", (rb_data.curr_file_num + 1) % RINGBUFFER_MAX_NUM_FILES);
  strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S", localtime(&current_time));
  rfile->name = g_strconcat(rb_data.fprefix, "_", filenum, "_", timestr,
			    rb_data.fsuffix, NULL);

  if (rfile->name == NULL) {
    if (err != NULL)
      *err = ENOMEM;
    return -1;
  }

  rb_data.fd = ws_open(rfile->name, O_RDWR|O_BINARY|O_TRUNC|O_CREAT,
                            rb_data.group_read_access ? 0640 : 0600);

  if (rb_data.fd == -1 && err != NULL) {
    *err = errno;
  }

  return rb_data.fd;
}
Пример #3
0
/*
 * Write out a list of filters.
 *
 * On success, "*pref_path_return" is set to NULL.
 * On error, "*pref_path_return" is set to point to the pathname of
 * the file we tried to read - it should be freed by our caller -
 * and "*errno_return" is set to the error.
 */
void
save_filter_list(filter_list_type_t list_type, char **pref_path_return,
    int *errno_return)
{
  const gchar *ff_name;
  gchar      *ff_path, *ff_path_new;
  GList      *fl;
  GList      *flpp;
  filter_def *filt;
  FILE       *ff;
  guchar     *p, c;

  *pref_path_return = NULL;	/* assume no error */

  switch (list_type) {

  case CFILTER_LIST:
    ff_name = CFILTER_FILE_NAME;
    fl = capture_filters;
    break;

  case DFILTER_LIST:
    ff_name = DFILTER_FILE_NAME;
    fl = display_filters;
    break;

  default:
    g_assert_not_reached();
    return;
  }

  ff_path = get_persconffile_path(ff_name, TRUE);

  /* Write to "XXX.new", and rename if that succeeds.
     That means we don't trash the file if we fail to write it out
     completely. */
  ff_path_new = g_strdup_printf("%s.new", ff_path);

  if ((ff = ws_fopen(ff_path_new, "w")) == NULL) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    g_free(ff_path_new);
    return;
  }
  flpp = g_list_first(fl);
  while (flpp) {
    filt = (filter_def *) flpp->data;

    /* Write out the filter name as a quoted string; escape any quotes
       or backslashes. */
    putc('"', ff);
    for (p = (guchar *)filt->name; (c = *p) != '\0'; p++) {
      if (c == '"' || c == '\\')
        putc('\\', ff);
      putc(c, ff);
    }
    putc('"', ff);

    /* Separate the filter name and value with a space. */
    putc(' ', ff);

    /* Write out the filter expression and a newline. */
    fprintf(ff, "%s\n", filt->strval);
    if (ferror(ff)) {
      *pref_path_return = ff_path;
      *errno_return = errno;
      fclose(ff);
      ws_unlink(ff_path_new);
      g_free(ff_path_new);
      return;
    }
    flpp = flpp->next;
  }
  if (fclose(ff) == EOF) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }

#ifdef _WIN32
  /* ANSI C doesn't say whether "rename()" removes the target if it
     exists; the Win32 call to rename files doesn't do so, which I
     infer is the reason why the MSVC++ "rename()" doesn't do so.
     We must therefore remove the target file first, on Windows.

     XXX - ws_rename() should be ws_stdio_rename() on Windows,
     and ws_stdio_rename() uses MoveFileEx() with MOVEFILE_REPLACE_EXISTING,
     so it should remove the target if it exists, so this stuff
     shouldn't be necessary.  Perhaps it dates back to when we were
     calling rename(), with that being a wrapper around Microsoft's
     _rename(), which didn't remove the target. */
  if (ws_remove(ff_path) < 0 && errno != ENOENT) {
    /* It failed for some reason other than "it's not there"; if
       it's not there, we don't need to remove it, so we just
       drive on. */
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }
#endif

  if (ws_rename(ff_path_new, ff_path) < 0) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }
  g_free(ff_path_new);
  g_free(ff_path);
}
Пример #4
0
/* capture child closed its side of the pipe, do the required cleanup */
void
capture_input_closed(capture_session *cap_session, gchar *msg)
{
  capture_options *capture_opts = cap_session->capture_opts;
  int  err;
  int  packet_count_save;

  g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture stopped!");
  g_assert(cap_session->state == CAPTURE_PREPARING || cap_session->state == CAPTURE_RUNNING);

  if (msg != NULL)
    simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", msg);

  if(cap_session->state == CAPTURE_PREPARING) {
    /* We didn't start a capture; note that the attempt to start it
       failed. */
    capture_callback_invoke(capture_cb_capture_failed, cap_session);
  } else {
    /* We started a capture; process what's left of the capture file if
       we were in "update list of packets in real time" mode, or process
       all of it if we weren't. */
    if(capture_opts->real_time_mode) {
      cf_read_status_t status;

      /* Read what remains of the capture file. */
      status = cf_finish_tail((capture_file *)cap_session->cf, &err);

      /* XXX: If -Q (quit-after-cap) then cf->count clr'd below so save it first */
      packet_count_save = cf_get_packet_count((capture_file *)cap_session->cf);
      /* Tell the GUI we are not doing a capture any more.
         Must be done after the cf_finish_tail(), so file lengths are
         correctly displayed */
      capture_callback_invoke(capture_cb_capture_update_finished, cap_session);

      /* Finish the capture. */
      switch (status) {

      case CF_READ_OK:
        if ((packet_count_save == 0) && !capture_opts->restart) {
          simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK,
            "%sNo packets captured!%s\n"
            "\n"
            "As no data was captured, closing the %scapture file!\n"
            "\n"
            "\n"
            "Help about capturing can be found at:\n"
            "\n"
            "       http://wiki.wireshark.org/CaptureSetup"
#ifdef _WIN32
            "\n\n"
            "Wireless (Wi-Fi/WLAN):\n"
            "Try to switch off promiscuous mode in the Capture Options!"
#endif
            "",
            simple_dialog_primary_start(), simple_dialog_primary_end(),
            cf_is_tempfile((capture_file *)cap_session->cf) ? "temporary " : "");
          cf_close((capture_file *)cap_session->cf);
        }
        break;
      case CF_READ_ERROR:
        /* Just because we got an error, that doesn't mean we were unable
           to read any of the file; we handle what we could get from the
           file. */
        break;

      case CF_READ_ABORTED:
        /* Exit by leaving the main loop, so that any quit functions
           we registered get called. */
        main_window_quit();
        break;
      }
    } else {
      /* first of all, we are not doing a capture any more */
      capture_callback_invoke(capture_cb_capture_fixed_finished, cap_session);

      /* this is a normal mode capture and if no error happened, read in the capture file data */
      if(capture_opts->save_file != NULL) {
        capture_input_read_all(cap_session, cf_is_tempfile((capture_file *)cap_session->cf),
          cf_get_drops_known((capture_file *)cap_session->cf), cf_get_drops((capture_file *)cap_session->cf));
      }
    }
  }

  if(capture_opts->show_info)
    capture_info_close();

  cap_session->state = CAPTURE_STOPPED;

  /* if we couldn't open a capture file, there's nothing more for us to do */
  if(capture_opts->save_file == NULL) {
    cf_close((capture_file *)cap_session->cf);
    return;
  }

  /* does the user wants to restart the current capture? */
  if(capture_opts->restart) {
    capture_opts->restart = FALSE;

    ws_unlink(capture_opts->save_file);

    /* if it was a tempfile, throw away the old filename (so it will become a tempfile again) */
    if(cf_is_tempfile((capture_file *)cap_session->cf)) {
      g_free(capture_opts->save_file);
      capture_opts->save_file = NULL;
    }

    /* ... and start the capture again */
    if (capture_opts->ifaces->len == 0) {
      collect_ifaces(capture_opts);
    }

    /* close the currently loaded capture file */
    cf_close((capture_file *)cap_session->cf);

    capture_start(capture_opts, cap_session);
  } else {
    /* We're not doing a capture any more, so we don't have a save file. */
    g_free(capture_opts->save_file);
    capture_opts->save_file = NULL;
  }
}
Пример #5
0
bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
{
    int                 tmp_fd;
    QString             follow_filter;
    const char          *hostname0 = NULL, *hostname1 = NULL;
    char                *port0 = NULL, *port1 = NULL;
    QString             server_to_client_string;
    QString             client_to_server_string;
    QString             both_directions_string;
    follow_stats_t      stats;
    tcp_stream_chunk    sc;
    size_t              nchars;
    gboolean is_tcp = FALSE, is_udp = FALSE;

    beginRetapPackets();
    resetStream();

    if (file_closed_)
    {
        QMessageBox::warning(this, tr("No capture file."), tr("Please make sure you have a capture file opened."));
        return false;
    }

    if (cap_file_.capFile()->edt == NULL)
    {
        QMessageBox::warning(this, tr("Error following stream."), tr("Capture file invalid."));
        return false;
    }

    proto_get_frame_protocols(cap_file_.capFile()->edt->pi.layers, NULL, &is_tcp, &is_udp, NULL, NULL, NULL, NULL);

    switch (follow_type_)
    {
    case FOLLOW_TCP:
        if (!is_tcp) {
            QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a TCP packet selected."));
            return false;
        }
        break;
    case FOLLOW_UDP:
        if (!is_udp) {
            QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a UDP packet selected."));
            return false;
        }
        break;
    case FOLLOW_SSL:
        /* we got ssl so we can follow */
        removeStreamControls();
        if (!epan_dissect_packet_contains_field(cap_file_.capFile()->edt, "ssl")) {
            QMessageBox::critical(this, tr("Error following stream."),
                               tr("Please make sure you have an SSL packet selected."));
            return false;
        }
        break;
    }

    if (follow_type_ == FOLLOW_TCP || follow_type_ == FOLLOW_SSL)
    {
        /* Create a new filter that matches all packets in the TCP stream,
           and set the display filter entry accordingly */
        reset_tcp_reassembly();
    } else {
        reset_udp_follow();
    }

    if (use_stream_index) {
        follow_filter = gchar_free_to_qstring(
            build_follow_index_filter((follow_type_ == FOLLOW_TCP) ? TCP_STREAM : UDP_STREAM));
    } else {
        follow_filter = gchar_free_to_qstring(build_follow_conv_filter(&cap_file_.capFile()->edt->pi));
    }
    if (follow_filter.isEmpty()) {
        QMessageBox::warning(this,
                             tr("Error creating filter for this stream."),
                             tr("A transport or network layer header is needed."));
        return false;
    }

    if (follow_type_ == FOLLOW_TCP || follow_type_ == FOLLOW_SSL)
    {
        /* Create a temporary file into which to dump the reassembled data
           from the TCP stream, and set "data_out_file" to refer to it, so
           that the TCP code will write to it.

           XXX - it might be nicer to just have the TCP code directly
           append stuff to the text widget for the TCP stream window,
           if we can arrange that said window not pop up until we're
           done. */
        gchar *data_out_filename;
        tmp_fd = create_tempfile(&data_out_filename, "follow");
        data_out_filename_ = data_out_filename;

        if (tmp_fd == -1) {
            QMessageBox::warning(this, "Error",
                                 "Could not create temporary file %1: %2",
                                 data_out_filename_, g_strerror(errno));
            data_out_filename_.clear();
            return false;
        }

        data_out_file = fdopen(tmp_fd, "w+b");
        if (data_out_file == NULL) {
            QMessageBox::warning(this, "Error",
                                 "Could not create temporary file %1: %2",
                                 data_out_filename_, g_strerror(errno));
            //ws_close(tmp_fd);
            ws_unlink(data_out_filename_.toUtf8().constData());
            data_out_filename_.clear();
            return false;
        }
    }

    /* append the negation */
    if(!previous_filter.isEmpty()) {
        filter_out_filter_ = QString("%1 and !(%2)")
                .arg(previous_filter).arg(follow_filter);
    }
    else
    {
        filter_out_filter_ = QString("!(%1)").arg(follow_filter);
    }

    switch (follow_type_)
    {
    case FOLLOW_TCP:
    {
        int stream_count = get_tcp_stream_count() - 1;
        ui->streamNumberSpinBox->blockSignals(true);
        ui->streamNumberSpinBox->setMaximum(stream_count);
        ui->streamNumberSpinBox->setValue(get_follow_index(TCP_STREAM));
        ui->streamNumberSpinBox->blockSignals(false);
        ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
        ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());

        break;
    }
    case FOLLOW_UDP:
    {
        /* data will be passed via tap callback*/
        if (!registerTapListener("udp_follow", &follow_info_,
                                 follow_filter.toUtf8().constData(),
                                 0, NULL, udp_queue_packet_data, NULL)) {
            return false;
        }

        int stream_count = get_udp_stream_count() - 1;
        ui->streamNumberSpinBox->blockSignals(true);
        ui->streamNumberSpinBox->setMaximum(stream_count);
        ui->streamNumberSpinBox->setValue(get_follow_index(UDP_STREAM));
        ui->streamNumberSpinBox->blockSignals(false);
        ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
        ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());

        break;
    }
    case FOLLOW_SSL:
        /* we got ssl so we can follow */
        if (!registerTapListener("ssl", &follow_info_,
                                 follow_filter.toUtf8().constData(), 0,
                                 NULL, ssl_queue_packet_data, NULL)) {
            return false;
        }
        break;
    }

    /* Run the display filter so it goes in effect - even if it's the
       same as the previous display filter. */
    emit updateFilter(follow_filter, TRUE);

    switch (follow_type_)
    {
    case FOLLOW_TCP:

        break;
    case FOLLOW_UDP:
    case FOLLOW_SSL:
        removeTapListeners();
        break;
    }

    if (follow_type_ == FOLLOW_TCP)
    {
        /* Check whether we got any data written to the file. */
        if (empty_tcp_stream) {
            QMessageBox::warning(this, "Error",
                                 "The packets in the capture file for that stream have no data.");
            //ws_close(tmp_fd);
            ws_unlink(data_out_filename_.toUtf8().constData());
            data_out_filename_.clear();
            return false;
        }

        /* Go back to the top of the file and read the first tcp_stream_chunk
         * to ensure that the IP addresses and port numbers in the drop-down
         * list are tied to the correct lines displayed by follow_read_stream()
         * later on (which also reads from this file).  Close the file when
         * we're done.
         *
         * We read the data now, before we pop up a window, in case the
         * read fails.  We use the data later.
         */

        rewind(data_out_file);
        nchars=fread(&sc, 1, sizeof(sc), data_out_file);
        if (nchars != sizeof(sc)) {
            if (ferror(data_out_file)) {
                QMessageBox::warning(this, "Error",
                                     QString(tr("Could not read from temporary file %1: %2"))
                                     .arg(data_out_filename_)
                                     .arg(g_strerror(errno)));
            } else {
                QMessageBox::warning(this, "Error",
                                     QString(tr("Short read from temporary file %1: expected %2, got %3"))
                                     .arg(data_out_filename_)
                                     .arg((unsigned long)sizeof(sc))
                                     .arg((unsigned long)nchars));

            }
            //ws_close(tmp_fd);
            ws_unlink(data_out_filename_.toUtf8().constData());
            data_out_filename_.clear();
            return false;
        }
        fclose(data_out_file);
        data_out_file = NULL;
    }

    /* Stream to show */
    follow_stats(&stats);

    if (stats.is_ipv6) {
        struct e_in6_addr ipaddr;
        memcpy(&ipaddr, stats.ip_address[0], 16);
        hostname0 = get_hostname6(&ipaddr);
        memcpy(&ipaddr, stats.ip_address[1], 16);
        hostname1 = get_hostname6(&ipaddr);
    } else {
        guint32 ipaddr;
        memcpy(&ipaddr, stats.ip_address[0], 4);
        hostname0 = get_hostname(ipaddr);
        memcpy(&ipaddr, stats.ip_address[1], 4);
        hostname1 = get_hostname(ipaddr);
    }

    switch (follow_type_)
    {
    case FOLLOW_TCP:
        port0 = tcp_port_to_display(NULL, stats.port[0]);
        port1 = tcp_port_to_display(NULL, stats.port[1]);
        break;
    case FOLLOW_UDP:
        port0 = udp_port_to_display(NULL, stats.port[0]);
        port1 = udp_port_to_display(NULL, stats.port[1]);
        break;
    case FOLLOW_SSL:
        port0 = tcp_port_to_display(NULL, stats.port[0]);
        port1 = tcp_port_to_display(NULL, stats.port[1]);
        break;
    }

    follow_info_.is_ipv6 = stats.is_ipv6;

    if (follow_type_ == FOLLOW_TCP)
    {
        /* Host 0 --> Host 1 */
        if ((sc.src_port == stats.port[0]) &&
            ((stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[0], 16) == 0)) ||
             (!stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[0], 4) == 0)))) {
            server_to_client_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname0).arg(port0)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname1).arg(port1)
                    .arg(gchar_free_to_qstring(format_size(
                                                   stats.bytes_written[0],
                                               format_size_unit_bytes|format_size_prefix_si)));
        } else {
            server_to_client_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname1).arg(port1)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname0).arg(port0)
                    .arg(gchar_free_to_qstring(format_size(
                                                   stats.bytes_written[0],
                                               format_size_unit_bytes|format_size_prefix_si)));
        }

        /* Host 1 --> Host 0 */
        if ((sc.src_port == stats.port[1]) &&
            ((stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[1], 16) == 0)) ||
             (!stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[1], 4) == 0)))) {
            client_to_server_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname0).arg(port0)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname1).arg(port1)
                    .arg(gchar_free_to_qstring(format_size(
                                                   stats.bytes_written[1],
                                               format_size_unit_bytes|format_size_prefix_si)));
        } else {
            client_to_server_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname1).arg(port1)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname0).arg(port0)
                    .arg(gchar_free_to_qstring(format_size(
                                                   stats.bytes_written[1],
                                               format_size_unit_bytes|format_size_prefix_si)));
        }

    }
    else
    {
        if ((follow_info_.client_port == stats.port[0]) &&
            ((stats.is_ipv6 && (memcmp(follow_info_.client_ip.data, stats.ip_address[0], 16) == 0)) ||
             (!stats.is_ipv6 && (memcmp(follow_info_.client_ip.data, stats.ip_address[0], 4) == 0)))) {
            server_to_client_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname0).arg(port0)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname1).arg(port1)
                    .arg(gchar_free_to_qstring(format_size(
                                                   follow_info_.bytes_written[0],
                                               format_size_unit_bytes|format_size_prefix_si)));

            client_to_server_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname1).arg(port1)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname0).arg(port0)
                    .arg(gchar_free_to_qstring(format_size(
                                                   follow_info_.bytes_written[1],
                                               format_size_unit_bytes|format_size_prefix_si)));
        } else {
            server_to_client_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname1).arg(port1)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname0).arg(port0)
                    .arg(gchar_free_to_qstring(format_size(
                                                   follow_info_.bytes_written[0],
                                               format_size_unit_bytes|format_size_prefix_si)));

            client_to_server_string =
                    QString("%1:%2 %3 %4:%5 (%6)")
                    .arg(hostname0).arg(port0)
                    .arg(UTF8_RIGHTWARDS_ARROW)
                    .arg(hostname1).arg(port1)
                    .arg(gchar_free_to_qstring(format_size(
                                                   follow_info_.bytes_written[1],
                                               format_size_unit_bytes|format_size_prefix_si)));
        }
    }

    wmem_free(NULL, port0);
    wmem_free(NULL, port1);

    /* Both Stream Directions */
    switch (follow_type_)
    {
    case FOLLOW_TCP:
        both_directions_string = QString("Entire conversation (%1)")
                .arg(gchar_free_to_qstring(format_size(
                                               stats.bytes_written[0] + stats.bytes_written[1],
                     format_size_unit_bytes|format_size_prefix_si)));
        setWindowSubtitle(tr("Follow TCP Stream (%1)").arg(follow_filter));
        break;
    case FOLLOW_UDP:
        both_directions_string = QString("Entire conversation (%1)")
                .arg(gchar_free_to_qstring(format_size(
                                               follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
                     format_size_unit_bytes|format_size_prefix_si)));
        setWindowSubtitle(tr("Follow UDP Stream (%1)").arg(follow_filter));
        break;
    case FOLLOW_SSL:
        both_directions_string = QString("Entire conversation (%1)")
                .arg(gchar_free_to_qstring(format_size(
                                               follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
                     format_size_unit_bytes|format_size_prefix_si)));
        setWindowSubtitle(tr("Follow SSL Stream (%1)").arg(follow_filter));
        break;
    }

    ui->cbDirections->clear();
    ui->cbDirections->addItem(both_directions_string);
    ui->cbDirections->addItem(client_to_server_string);
    ui->cbDirections->addItem(server_to_client_string);

    followStream();
    fillHintLabel(-1);

    if (data_out_file) {
        fclose(data_out_file);
        data_out_file = NULL;
    }

    endRetapPackets();
    return true;
}
Пример #6
0
/*
 * Write out a list of disabled protocols.
 *
 * On success, "*pref_path_return" is set to NULL.
 * On error, "*pref_path_return" is set to point to the pathname of
 * the file we tried to read - it should be freed by our caller -
 * and "*errno_return" is set to the error.
 */
void
save_disabled_protos_list(char **pref_path_return, int *errno_return)
{
  gchar       *ff_path, *ff_path_new;
  FILE        *ff;
  gint         i;
  protocol_t  *protocol;
  void        *cookie;

  *pref_path_return = NULL;	/* assume no error */

  ff_path = get_persconffile_path(PROTOCOLS_FILE_NAME, TRUE, TRUE);

  /* Write to "XXX.new", and rename if that succeeds.
     That means we don't trash the file if we fail to write it out
     completely. */
  ff_path_new = g_strdup_printf("%s.new", ff_path);

  if ((ff = ws_fopen(ff_path_new, "w")) == NULL) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    g_free(ff_path_new);
    return;
  }

  /* Iterate over all the protocols */

  for (i = proto_get_first_protocol(&cookie); i != -1;
       i = proto_get_next_protocol(&cookie)) {

    if (!proto_can_toggle_protocol(i)) {
      continue;
    }

    protocol = find_protocol_by_id(i);
    if (proto_is_protocol_enabled(protocol)) {
      continue;
    }

    /* Write out the protocol name. */
    fprintf(ff, "%s\n", proto_get_protocol_filter_name(i));
  }

  if (fclose(ff) == EOF) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }

#ifdef _WIN32
  /* ANSI C doesn't say whether "rename()" removes the target if it
     exists; the Win32 call to rename files doesn't do so, which I
     infer is the reason why the MSVC++ "rename()" doesn't do so.
     We must therefore remove the target file first, on Windows.

     XXX - ws_rename() should be ws_stdio_rename() on Windows,
     and ws_stdio_rename() uses MoveFileEx() with MOVEFILE_REPLACE_EXISTING,
     so it should remove the target if it exists, so this stuff
     shouldn't be necessary.  Perhaps it dates back to when we were
     calling rename(), with that being a wrapper around Microsoft's
     _rename(), which didn't remove the target. */
  if (ws_remove(ff_path) < 0 && errno != ENOENT) {
    /* It failed for some reason other than "it's not there"; if
       it's not there, we don't need to remove it, so we just
       drive on. */
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }
#endif

  if (ws_rename(ff_path_new, ff_path) < 0) {
    *pref_path_return = ff_path;
    *errno_return = errno;
    ws_unlink(ff_path_new);
    g_free(ff_path_new);
    return;
  }
  g_free(ff_path_new);
  g_free(ff_path);
}