Logo Search packages:      
Sourcecode: efax-gtk version File versions

socket_server.cpp

/* Copyright (C) 2003 2004 Chris Vine

This program is distributed under the General Public Licence, version 2.
For particulars of this and relevant disclaimers see the file
COPYING distributed with the source files.

*/

#include <cstdlib>
#include <fstream>
#include <vector>
#include <algorithm>
#include <cstring>

#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

#include <glibmm/convert.h>

#include "socket_server.h"

#ifdef HAVE_PTHREAD_SIGMASK 
#include <pthread.h>
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
#endif

#define SAVED_FAX_FILENAME  ".efax-gtk_queued_server_files"

#define BUFFER_LENGTH 1024


Socket_server::Socket_server(void): server_running(false), closing_socket(false),
                            serve_sock_fd(-1), count (0),
                                    working_dir(prog_config.working_dir),
                            filenames_s(new(FilenamesList)) {
  // we have included std::string working_dir member so that we can access it in the
  // socket server thread created by Socket_server::start() without having to
  // introduce locking of accesses to prog_config.working_dir by the main GUI thread
  // it is fine to initialise it in the initialisation list of this constructor
  // as prog_config.working_dir is only set once, on the first call to configure_prog()
  // (that is, when configure_prog() is passed false as its argument)


  fax_to_send.second = 0;

  stdout_notify.connect(sigc::mem_fun(*this, &Socket_server::write_stdout_dispatcher_slot));

  // make sure that we have the $HOME/efax-gtk-server directory
  // (we do not need to lock working_dir_mutex as the socket server thread cannot
  // be running at this point)
  std::string dir_name(working_dir);
  dir_name += "/efax-gtk-server";
  mkdir(dir_name.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);

  // now populate the queued faxes from server list  
  read_queued_faxes();
}

void Socket_server::start(const std::string& port_, bool other_sock_client_address_) {

  if (!server_running) {
    port = port_;
    other_sock_client_address = other_sock_client_address_;
    server_running = true;

    // now block off the signals for which we have set handlers so that the socket server
    // thread does not receive the signals, otherwise we will have memory synchronisation
    // issues in multi-processor systems - we will unblock in the initial (GUI) thread
    // as soon as the socket server thread has been launched
    sigset_t sig_mask;
    sigemptyset(&sig_mask);
    sigaddset(&sig_mask, SIGCHLD);
    sigaddset(&sig_mask, SIGQUIT);
    sigaddset(&sig_mask, SIGTERM);
    sigaddset(&sig_mask, SIGINT);
    sigaddset(&sig_mask, SIGHUP);
    sigaddset(&sig_mask, SIGUSR2);
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_BLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_BLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif

    try {
      Glib::Thread::create(sigc::mem_fun(*this, &Socket_server::socket_thread), false);
    }
    catch (Glib::ThreadError&) {
      write_error("Cannot start new socket thread, fax socket will not run\n");
      server_running = false;
    }
    // now unblock the signals so that the initial (GUI) thread can receive them
#ifdef HAVE_PTHREAD_SIGMASK 
    pthread_sigmask(SIG_UNBLOCK, &sig_mask, 0);
#else
    while (sigprocmask(SIG_UNBLOCK, &sig_mask, 0) == -1 && errno == EINTR);
#endif
  }
}

void Socket_server::socket_thread(void) {

  { // mutex critical section
    Glib::Mutex::Lock lock(socket_mutex);
    if ((serve_sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      write_error("Cannot create socket for socket server\n");
      server_running = false;
      return;
    }

    sockaddr_in serve_address;
    std::memset(&serve_address, 0, sizeof(serve_address));

    serve_address.sin_family = AF_INET;
    if (other_sock_client_address) serve_address.sin_addr.s_addr = htonl(INADDR_ANY);
    else {
      long localhost_address = inet_addr("127.0.0.1");
      if (localhost_address > -1) serve_address.sin_addr.s_addr = localhost_address;
      else {
      write_error("Cannot create network address for localhost\n");
      while (close(serve_sock_fd) == -1 && errno == EINTR);
      serve_sock_fd = -1;
      server_running = false;
      return;
      }
    }
    serve_address.sin_port = htons(std::atoi(port.c_str()));
    
    if ((bind(serve_sock_fd, (sockaddr*)&serve_address, sizeof(serve_address))) < 0) {
      write_error("Cannot bind to socket for socket server\n");
      while (close(serve_sock_fd) == -1 && errno == EINTR);
      serve_sock_fd = -1;
      server_running = false;
      return;
    }

    if ((listen(serve_sock_fd, 5)) < 0) {
      write_error("Cannot listen on socket of socket server\n");
      while (close(serve_sock_fd) == -1 && errno == EINTR);
      serve_sock_fd = -1;
      server_running = false;
      return;
    }
  } // end of mutex critical section

  std::string message(gettext("Socket running on port "));
  message += port;
  message += '\n';
  write_stdout_from_thread(message.c_str());

  // now wait for clients to connect, and continue
  while (accept_on_client());

  write_stdout_from_thread("Closing the socket\n");
  server_running = false;
  close_sem.post();
}

bool Socket_server::accept_on_client(void) {

  bool return_val = false;

  sockaddr_in connect_address;
  socklen_t client_len = sizeof(connect_address);

  int connect_sock_fd;
  while ((connect_sock_fd = accept(serve_sock_fd, (sockaddr*)&connect_address, &client_len))
       == -1 && errno == EINTR);

  // go into mutex critical section here
  Glib::Mutex::Lock lock(socket_mutex);
  if (connect_sock_fd >= 0
      && is_valid_peer(connect_address)) {

    std::string filename;
    {
      Glib::Mutex::Lock lock(working_dir_mutex);
      filename = working_dir;
    }
    filename += "/efax-gtk-server/efax-gtk-server-XXXXXX";

    std::string::size_type size = filename.size() + 1;
    char* tempfile = new char[size];
    std::memcpy(tempfile, filename.c_str(), size); // this will include the terminating '\0' in the copy
    int file_fd = mkstemp(tempfile);

    if (file_fd < 0) {
      
      write_error("Can't open temporary file for socket server\n"
              "This fax will not be processed.  Is the disk full?\n");
      while (close(connect_sock_fd) == -1 && errno == EINTR);
      stop();
      return_val = false;
    }
    else read_socket(file_fd, connect_sock_fd, tempfile);
    delete[] tempfile;
    while (close(connect_sock_fd) == -1 && errno == EINTR);
    while (close(file_fd) == -1 && errno == EINTR);
    return_val = true;
  }

  else if (connect_sock_fd >= 0) {
    // if we have got here then the peer is invalid - reject it by
    // closing connect_sock_fd
    write_error("Invalid host tried to connect to the socket server\n");
    while (close(connect_sock_fd) == -1 && errno == EINTR);
    return_val = true;
  }

  else if (closing_socket) closing_socket = false; // stop() has been called

  else{
    // if we have got here, connect_sock_fd is in error condition (otherwise it
    // would have been picked up in the previous else_if blocks) - report the
    // problem
    write_error("There is a problem with receiving a connection on the socket server\n");
    return_val = true;
  }
  return return_val;
}

void Socket_server::read_socket(int file_fd, int connect_sock_fd, const char* file) {

  char buffer[BUFFER_LENGTH];
  ssize_t read_result;
  ssize_t write_result;
  ssize_t written;

  while ((read_result = read(connect_sock_fd, buffer, BUFFER_LENGTH)) > 0
       || (read_result == -1 && errno == EINTR)) {
    if (read_result > 0) {
      written = 0;
      do {
      write_result = write(file_fd, buffer + written, read_result);
      if (write_result > 0) {
        written += write_result;
        read_result -= write_result;
      }
      } while (read_result && (write_result != -1 || errno == EINTR));
    }
  }

  if (!read_result) {                                   // normal close by client
  
    write_stdout_from_thread(gettext("Print job received on socket\n"));
    if (count < 9999) count++;
    else count = 1;
    
    add_file(file);
    filelist_changed_notify();

    Glib::Mutex::Lock lock(fax_to_send_mutex);
    // use a Glib::Cond object to avoid overwriting an earlier fax to send entry
    while (fax_to_send.second) fax_to_send_cond.wait(fax_to_send_mutex);

    fax_to_send.first = file;
    fax_to_send.second = count;
    fax_to_send_notify();
  }
  else write_error("Error in receiving print job on socket\n");
}

void Socket_server::stop(void) {

  if (serve_sock_fd >= 0) {
    { // mutex critical section
      Glib::Mutex::Lock lock(socket_mutex);
      closing_socket = true;
      shutdown(serve_sock_fd, SHUT_RDWR);
      while (close(serve_sock_fd) == -1 && errno == EINTR);
      serve_sock_fd = -1;
    } // end of mutex critical section

    close_sem.wait();
  }
}

void Socket_server::add_file(const std::string& filename) {

  Glib::Mutex::Lock lock(filenames_mutex);
  std::pair<std::string, unsigned int> file_item;
  file_item.first = filename;
  file_item.second = count;
  filenames_s->push_back(file_item);

  save_queued_faxes();
}
 
std::pair<Shared_ptr<FilenamesList>, Shared_ptr<Glib::Mutex::Lock> > Socket_server::get_filenames(void) const {

  // we will use a Shared_ptr, which automatically controls object life, to hold a Glib::Mutex::Lock
  // this tunnelling lock will automatically be released once the object using the FilenamesList
  // object has finished with it and the lock is deleted by the destructor of the last Shared_ptr
  // holding it (namely when SocketListDialog::set_socket_list_rows() has completed its task)
  Shared_ptr<Glib::Mutex::Lock> lock_s(new Glib::Mutex::Lock(filenames_mutex));
  return std::pair<Shared_ptr<FilenamesList>, Shared_ptr<Glib::Mutex::Lock> >(filenames_s, lock_s);
}

std::pair<std::string, unsigned int> Socket_server::get_fax_to_send(void) {

  Glib::Mutex::Lock lock(fax_to_send_mutex);
  
  std::pair<std::string, unsigned int> return_val(fax_to_send);

  fax_to_send.second = 0;
  // release the Glib::Cond object if it is blocking
  fax_to_send_cond.signal();
  return return_val;
}  

int Socket_server::remove_file(const std::string& filename) {

  Glib::Mutex::Lock lock(filenames_mutex);

  int return_val = 0;
  FilenamesList::iterator iter;
  bool found_file = false;
  for (iter = filenames_s->begin(); !found_file && iter != filenames_s->end(); ++iter) {
    if (iter->first == filename) {
      filenames_s->erase(iter);
      found_file = true;
      filelist_changed_notify();
      // clean up by deleting the temporary file we created earlier
      // in accept_on_client() above
      unlink(filename.c_str());
    }
  }
  if (!found_file) return_val = -1;
  else save_queued_faxes();
  return return_val;
}

void Socket_server::read_queued_faxes(void) {

  // we do not need a mutex here as it is only called in
  // Socket_server::Socket_server() before a new thread
  // has been launched

  // we do not need to use working_dir_mutex here either, for the same reason
  std::string filename(working_dir);
  filename += "/" SAVED_FAX_FILENAME;
  
  std::ifstream filein(filename.c_str(), std::ios::in);

  if (filein) {

    filenames_s->clear(); // may sure the files list is empty

    std::string file_read;
    
    while (std::getline(filein, file_read)) {
      if (!file_read.empty() && file_read[0] != '#' && file_read[0] != '!') {

      // find the last '?' (in theory, mkstemp() could use it in the filename,
      // so there may be a preceding one, so use a reverse find)
      std::string::size_type pos = file_read.rfind('?');
      if (pos != std::string::npos
          && file_read.size() > pos + 1) {
        if (!access(file_read.substr(0, pos).c_str(), R_OK)) {    // file exists and can be read
          std::pair<std::string, unsigned int> file_item;
          // file_item.first is the filename
          // file_item second is the print job number
          file_item.first = file_read.substr(0, pos);
          file_item.second = std::atoi(file_read.substr(pos + 1).c_str());
          filenames_s->push_back(file_item);
        }
      }
      }
      else if (file_read[0] == '!' && file_read.size() > 1) {
      count = std::atoi(file_read.substr(1).c_str());
      }
    }
  }
}

void Socket_server::save_queued_faxes(void) {

  // we do not need a filenames mutex here as this method is only called by
  // Socket_server::add_file() and Socket_server::remove_file()
  // which are already protected by a mutex

  std::string filename;
  {
    Glib::Mutex::Lock lock(working_dir_mutex);
    filename = working_dir;
  }
  filename += "/" SAVED_FAX_FILENAME;
  
  std::ofstream fileout(filename.c_str(), std::ios::out);
  if (!fileout) {
    std::string message("Can't open file ");
    message += filename;
    message += ", so the list of queued files can't be saved\n";
    write_error(message.c_str());
  }
  else {
    FilenamesList::const_iterator iter;
    for (iter = filenames_s->begin(); iter != filenames_s->end(); ++iter) {
      fileout << iter->first << '?' << iter->second << '\n';
    }
    fileout << '!' << count << '\n';
  }
}

void Socket_server::write_stdout_from_thread(const char* message) {

  Glib::Mutex::Lock lock(stdout_mutex);

  // use a Glib::Cond object to avoid overwriting an earlier message
  while (!stdout_text.empty()) stdout_cond.wait(stdout_mutex);

  stdout_text = message;
  stdout_notify();
}

void Socket_server::write_stdout_dispatcher_slot(void) {

  Glib::Mutex::Lock lock(stdout_mutex);

  stdout_message(stdout_text.c_str());
  stdout_text = "";

  // release the Glib::Cond object if it is blocking
  stdout_cond.signal();
}

bool Socket_server::is_valid_peer(const sockaddr_in& address) {

  bool return_val = false;
  hostent* hostinfo_p;

  // gethostbyaddr is not guaranteed by IEEE Std 1003.1 to be thread safe -
  // that doesn't matter as this program only uses it in this thread
  if ((hostinfo_p = gethostbyaddr((const char*)&address.sin_addr.s_addr,
                          sizeof(address.sin_addr.s_addr), AF_INET)) == 0) {
    write_error("Cannot get hostname of peer\n");
  }

  else {
    // we need to store the value of hostinfo_p->h_name,
    // as gethostbyaddr() is called again below
    std::string peer_hostname(hostinfo_p->h_name);
    std::vector<std::string> valid_names;
    valid_names.push_back("localhost");          // localhost is always valid
    // include the fully qualified localhost address
    hostent* my_info_p;
    long localhost_address = inet_addr("127.0.0.1");
    if (localhost_address == -1) {
      write_error("Cannot create network address for localhost\n");
    }
    else if ((my_info_p = gethostbyaddr((const char*)&localhost_address,
                              sizeof(address.sin_addr.s_addr), AF_INET)) == 0) {
      write_error("Cannot get the fully qualified localhost name\n");
    }
    else {
      valid_names.push_back(my_info_p->h_name);  // and our fq localhost name is also valid
    }

    char my_hostname_p[256];
    if (gethostname(my_hostname_p, 256) < 0) {
      write_error("Cannot get our hostname\n");
    }
    else {
      my_hostname_p[255] = 0; // make sure it is null terminated
      valid_names.push_back(my_hostname_p);       // our own hostname is also always valid
      // gethostbyname is not guaranteed by IEEE Std 1003.1 to be thread safe -
      // that doesn't matter as this program only uses it in this thread
      if ((my_info_p = gethostbyname(my_hostname_p)) == 0) {
      write_error("Cannot get our fully qualified hostname\n");
      }
      else {
      valid_names.push_back(my_info_p->h_name); // and our fq hostname is also valid
      }
    }

    // and now add the permitted clients from the settings dialog
    // lock the Prog_config object to stop it being modified or accessed in the initial
    // (GUI) thread while we are accessing it here
    Glib::Mutex::Lock lock(*prog_config.mutex_p);
    std::copy(prog_config.permitted_clients_list.begin(),
            prog_config.permitted_clients_list.end(),
            std::back_inserter(valid_names));
    std::vector<std::string>::const_iterator iter;
    for (iter = valid_names.begin(); !return_val && iter != valid_names.end(); ++iter) {
      if (*iter == peer_hostname) return_val = true;
    }
  }
  return return_val;
}

Generated by  Doxygen 1.6.0   Back to index