Logo Search packages:      
Sourcecode: maildir-bulletin version File versions  Download package

maildir-bulletin.cpp

#include <stdio.h>
#include <string>
#include <strings.h>
#include <string.h>
#include <grp.h>
#include <pwd.h>
//#ifdef _AIX
//#include <sys/timers.h>
//#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <set>
#include <unistd.h>
#include <errno.h>

#include "maildir-bulletin.h"
#include "mb_util.h"

using namespace std;

00024 struct ltstr
{
  bool operator()(const string &s1, const string &s2) const
  {
    return s1 < s2;
  }
};

typedef set<string, ltstr> STRING_SET;

int addBul(CPCCHAR user, CPCCHAR dir, CPCCHAR file, CPCCHAR baseName
         , mb_log &log, bool symLink);
int bufferFile(mb_log &log, string &data, CPCCHAR file = NULL);
int check_legal_groups(const string &message, int argc, char *argv[], mb_log &log);
int get_admin_addr(int argc, char *argv[], char ***admin, mb_log &log);
int get_to_field(int argc, char *argv[], char ***to_field, mb_log &log);
int pipeit(int *input, int output, char *argv[], mb_log &log);
int dir_sync(CPCCHAR dirName, mb_log &log);

void usage()
{
  printf("Usage: maildir-bulletin [-s] group [more groups]\n"
         "Sends bulletin message to all users in one (or more) groups.\n"
         "The \"-s\" parameter tells it to use sym-links not hard links.\n"
         "\nVersion: " BUL_VERSION "\n");
  exit(1);
}

int main(int argc, char *argv[])
{
  if(argc == 1)
  {
    usage();
  }

  STRING_SET users;
  STRING_SET::const_iterator uend = users.end();

  if(chdir(HOMEDIR))
  {
    printf("Can't change to directory " HOMEDIR "\n");
    return 1;
  }
  time_t t = time(NULL);
  struct tm *loc = NULL;
  loc = localtime(&t);
  mb_log log(LOG_FILE, ERROR_FILE, loc);


  string message;
  if(bufferFile(log, message))
    return 1;

  int expYear = 0, expMonth = 0, expDay = 0;
  {
    const char *const find = "\nBULLETIN_EXPIRES:";
    size_t start = message.find(find);
    if(int(start) != -1)
    {
      size_t expPos = start + strlen(find);
      while(message[expPos] == ' ')
        expPos++;
      size_t end = message.find('\n', expPos);
      if(int(end) == -1)
        end = message.size();
      string s;
      s.append(message, expPos, end - expPos);
      message.erase(start, end - start);
      if(sscanf(s.c_str(), "%d-%d-%d", &expYear, &expMonth, &expDay) != 3)
      {
        expYear = 0;
      }
      else
      {
        if(expYear < 60)
          expYear += 2000;
        else if(expYear < 1900)
          expYear += 1900;

        if(expYear < loc->tm_year + 1900
         || (expYear == loc->tm_year + 1900
            && (expMonth < loc->tm_mon + 1
                || (expMonth == loc->tm_mon + 1 && expDay <= loc->tm_mday) ) ) )
        {
          expYear = 0;
        }
      }
    }
  }

  bool symLink = false;
  int i;
  for(i = 0; i < argc; i++)
  {
    if(!strcasecmp(argv[i], "-s"))
    {
      if(symLink || argc == 2)
        usage();
      symLink = true;
    }
  }

  if(check_legal_groups(message, argc, argv, log))
    return 1;

  char **admin;
  char **tofield;
  if(get_admin_addr(argc, argv, &admin, log)
   || get_to_field(argc, argv, &tofield, log))
  {
    return 1;
  }

  char bullBaseName[1024]; // The name that appears in the user's maildir, and
                           // the base part of the name used for the system.
  sprintf(bullBaseName, "%04d-%02d-%02d:%02d:%02d:%02d.%d"
                  , loc->tm_year + 1900, loc->tm_mon + 1, loc->tm_mday
                  , loc->tm_hour, loc->tm_min, loc->tm_sec, getpid());
  for(i = 1; i < argc; i++)
  {
    if(strcasecmp("-s", argv[i]) )
    {
      group *gr = getgrnam(argv[i]);
      if(gr == NULL && strcasecmp(argv[i], "all"))
      {
        log.error("Can't find group %s\n", argv[i]);
        return 1;
      }

      char fileName[1024];
      {
        char sedName[1024];

        sprintf(sedName, HOMEDIR "/tmp/sed.%d", getpid());
        FILE *fp = fopen(sedName, "w");
        if(!fp)
        {
          log.error("Can't create temp file %s.\n", sedName);
          return 1;
        }
        fprintf(fp, "1,/^$/ {\n"
                "    /^[Ss]ender:/s//X-Original-Sender:/\n"
                "    /^[Ee]rrors-[Tt]o:/d\n"
                "    /^[Ss]tatus:/d\n"
                "    /^[Tt]o:/d\n"
                "    /^[Pp]recedence:/d\n"
                "    /^[Pp]riority:/d\n"
                "    /^$/i\\\n"
                "To: %s\\\n"
                "Sender: %s\n"
                "    }", tofield[i], admin[i]);
        fclose(fp);
        if(expYear)
        {
          sprintf(fileName, BULLETINSDIR "/%s:%s:%d-%d-%d", bullBaseName
                          , argv[i], expYear, expMonth, expDay);
        }
        else
          sprintf(fileName, BULLETINSDIR "/%s:%s", bullBaseName, argv[i]);
        int perms = S_IRUSR | S_IWUSR | S_IRGRP;
        if(!strcasecmp(argv[i], "all"))
          perms |= S_IROTH;
        int fh = creat(fileName, perms);
        if(fh == -1)
        {
          log.error("Can't create the bulletin file %s\n", fileName);
          return 1;
        }
        int input;
        char *sedargv[4];
        sedargv[0] = strdup(SED);
        sedargv[1] = strdup("-f");
        sedargv[2] = sedName;
        sedargv[3] = NULL;
        int rc = pipeit(&input, fh, sedargv, log);
        free(sedargv[0]);
        free(sedargv[1]);
        if(rc)
          return 1;
        if((int)message.size()
           != write(input, (const void *)message.c_str(), message.size()))
        {
          log.error("Can't write the bulletin file %s\n", fileName);
          return 1;
        }
        close(input);
        int gid;
        if(gr)
          gid = gr->gr_gid;
        else
          gid = 0;
        if(fchmod(fh, perms) || fchown(fh, 0, gid))
        {
          log.error("Can't change permissions of bulletin file %s\n", fileName);
          return 1;
        }
        wait(NULL);
        fsync(fh);
        close(fh);
        unlink(sedName);
        if(dir_sync(BULLETINSDIR, log))
          return 1;
      }

      struct passwd *pw = NULL;
      if(gr)
      {
        for(int j = 0; gr->gr_mem[j] != NULL; j++)
        {
          if(users.find(gr->gr_mem[j]) == uend)
          {
            pw = getpwnam(gr->gr_mem[j]);
            if(!pw)
            {
              log.error("No user %s\n", gr->gr_mem[j], "", false);
            }
            else
            {
              addBul(gr->gr_mem[j], pw->pw_dir, fileName, bullBaseName, log, symLink);
            }
            users.insert(gr->gr_mem[j]);
          }
        }
      }
      setpwent();
      while( (pw = getpwent()) )
      {
        // if group "all" else if user is in the group
        if(!gr)
        {
          addBul(pw->pw_name, pw->pw_dir, fileName, bullBaseName, log, symLink);
        }
        else if(pw->pw_gid == gr->gr_gid)
        {
          if(users.find(pw->pw_name) == uend)
          {
            users.insert(pw->pw_name);
            addBul(pw->pw_name, pw->pw_dir, fileName, bullBaseName, log, symLink);
          }
        }
      }
    } // end of if(strcasecmp(...)) which ensures we don't treat -s as a group
  } // end of the main for loop that goes through argv[]
  return 0;
}

int dir_sync(CPCCHAR dirName, mb_log &log)
{
  int fh;
  if((fh = open(dirName, O_RDONLY)) < 0)
  {
    log.error("Can't open directory %s\n", dirName);
    return 1;
  }
  if(fsync(fh) < 0 && errno == EIO)
  {
    log.error("Error fsyncing directory %s\n", dirName);
    close(fh);
    return 1;
  }
  close(fh);
  return 0;
}

int pipeit(int *input, int output, char *argv[], mb_log &log)
{
  int rp[2];

  if(pipe(rp))
  {
    log.error("Can't create pipe.\n");
    return 1;
  }
  int f = fork();
  if(f == -1)
  {
    log.error("Can't fork a new process.\n");
    return 1;
  }
  if(f == 0)
  {
    // in child
    close(rp[1]);
    dup2(output, 1);
    dup2(rp[0], 0);
    execv(argv[0], argv);
  }
  else
  {
    close(rp[0]);
    *input = rp[1];
  }
  return 0;
}

int addBul(CPCCHAR user, CPCCHAR dir, CPCCHAR file, CPCCHAR baseName
         , mb_log &log, bool symLink)
{
  char dirName[1024];

  if(!strcmp(dir, "/tmp") || !strcmp(dir, "/dev/null"))
    return 0; // don't deliver to such system accounts

  snprintf(dirName, sizeof(dirName), "%s/Maildir/new", dir);
  if(chdir(dirName))
  {
    log.error("Can't change to user %s's mail directory.\n", user);
    return 1;
  }
  int rc = 0;
  if(!symLink)
    rc = link(file, baseName);
  if(rc || symLink)
  {
    if(symlink(file, baseName))
    {
      log.error("Can't create link for user %s.\n", user);
      return 1;
    }
  }
  if(dir_sync(dirName, log))
    return 1;
  return 0;
}

// Get a line from a configuration file which is in the format of
// KEY SPACE VALUE
// returns a pointer to the value if the line is non-blank, NULL otherwise
char *get_config_item(FILE *fp, char *buf, int bufLen)
{
  if(!fgets(buf, bufLen, fp))
    return NULL;
  strtok(buf, "\n");

  int len = strlen(buf);
  char *value = NULL;
  for(int i = 0; i < len; i++)
  {
    if(buf[i] == ' ' || buf[i] == '\t')
    {
      buf[i] = '\0';
      value = NULL;
    }
    else if(!value)
    {
      value = &buf[i];
    }
  }
  return value;
}

int get_admin_addr(int argc, char *argv[], char ***admin, mb_log &log)
{
  char **adm = new PCHAR[argc];
  *admin = adm;
  adm[0] = NULL;
  FILE *fp = fopen(ADMIN, "r");
  if(!fp)
  {
    log.error("Can't open file " ADMIN "\n");
    return 1;
  }
  char buf[1024];
  char *value;
  int i;
  for(i = 0; i < argc; i++)
    adm[i] = NULL;

  while( (value = get_config_item(fp, buf, sizeof(buf))) )
  {
    if(!strcasecmp(value, "ALL"))
    {
      if(!adm[0])
        adm[0] = strdup(buf);
    }
    else
    {
      for(i = 1; i < argc; i++)
      {
        if(!adm[i] && !strcmp(argv[i], value))
          adm[i] = strdup(buf);
      }
    }
  }
  fclose(fp);
  for(i = 1; i < argc; i++)
    adm[i] = adm[0];
  for(i = 1; i < argc; i++)
  {
    if(!adm[i])
    {
      log.error("Can't get administrator for group %s.\n", argv[i]);
      return 1;
    }
  }
  return 0;
}

int get_to_field(int argc, char *argv[], char ***to_field, mb_log &log)
{
  char **to = new PCHAR[argc];
  *to_field = to;
  to[0] = NULL;
  FILE *fp = fopen(TOFIELD, "r");
  if(!fp)
  {
    log.error("Can't open file " TOFIELD "\n");
    return 1;
  }
  char buf[1024];
  char *value;
  int i;
  buf[1023] = '\0';
  while( fgets(buf, sizeof(buf), fp) )
  {
    strtok(buf, "\n");
    value = NULL;
    for(i = 0; i < int(strlen(buf)); i++)
    {
      if(buf[i] == ' ')
      {
        buf[i] = '\0';
        value = &buf[i + 1];
        break;
      }
    }
    if(!value)
    {
      log.error("Bad " TOFIELD " line:%s\n", buf);
      return 1;
    }

    for(i = 1; i < argc; i++)
    {
      if(!strcmp(argv[i], buf))
        to[i] = strdup(value);
    }
  }
  fclose(fp);
  for(i = 1; i < argc; i++)
  {
    if(!to[i])
    {
      log.error("Can't get to field for group %s.\n", argv[i]);
      return 1;
    }
  }
  return 0;
}

int check_legal_groups(const string &message, int argc, char *argv[], mb_log &log)
{
  STRING_SET groups_set;
  int tmpPos;

  tmpPos = message.rfind("From ", 0);
  if(tmpPos == 0)
  {
    tmpPos = 5;
  }
  else
  {
    // must be Qmail
    tmpPos = message.find("From: ");
    if(tmpPos == -1)
    {
      log.error("Can't find From field\n");
      return 1;
    }
    tmpPos += 6; // skip the "From: "
  }
  int end = message.find("\n", tmpPos);
  if(end == -1)
    end = 0;
  int space = message.find("\n", tmpPos);
  if(space != -1 && (space < end || end == 0) )
    end = space;
  string from;
  from.append(message, tmpPos, end - tmpPos);

  FILE *fp = fopen(ALLOWEDSENDERS, "r");
  if(!fp)
  {
    log.error("Can't open file " ALLOWEDSENDERS "\n");
    return 1;
  }
  char buf[1024];
  char *value;
  while( (value = get_config_item(fp, buf, sizeof(buf))) )
  {
    // if the from string matches the first field in the ALOWEDSENDERS file
    if((int)from.find(buf) != -1)
    {
      if(strcasecmp(value, "all") == 0) return 0;
      groups_set.insert(value);
    }
  }
  fclose(fp);
  for(int i = 1; i < argc; i++)
  {
    if(strcasecmp("-s", argv[i]) )
    {
      if(strlen(argv[i]) > 20)
      {
        log.error("Group name longer than 20 characters.\n");
        return 1;
      }
      if(groups_set.find(argv[i]) == groups_set.end())
      {
        log.error("User %s is not allowed to send to %s\n", from.c_str(), argv[i]);
        return 1;
      }
    }
  }
  return 0;
}

int bufferFile(mb_log &log, string &data, CPCCHAR file)
{
  char buf[1024];
  int len = 0;
  int fh = 0;

  if(file != NULL)
  {
    fh = open(file, O_RDONLY);
    if(fh == -1)
    {
      log.error("Can't open %s.\n", file);
      return 1;
    }
  }
  while( (len = read(fh, (void *)buf, sizeof(buf))) > 0 )
  {
    if(len < 0)
    {
      log.error("Error reading message.\n");
      return 1;
    }
    data.append(buf, len);
  }
  if(file)
    close(fh);
  return 0;
}

Generated by  Doxygen 1.6.0   Back to index