/****************************-*-C-*-***********************************
 * $Id: bksh.c,v 1.50 2004/03/05 02:40:18 anarcat Exp $
 **********************************************************************
 * Backup wrapper shell for ssh
 **********************************************************************
 *   Copyright (C) 2001-2003 The Anarcat <anarcat@anarcat.ath.cx>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  See also http://www.fsf.org
 *********************************************************************/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>

#if defined(__FreeBSD__) && __FreeBSD__ >= 2
#include <osreldate.h>
#    if __FreeBSD_version >= 430000
#include <libgen.h>
#    else
#include "basename.h"
#    endif
#else
/* compatibility with FreeBSD extensions */
#    if defined(__NetBSD__)
int optreset = 0;
#include "basename.h"
#    else
char optreset = 0;
#include "basename.h"
#    endif
#endif

#include "bksh.h"

/* recursively create missing directories */
int mkdirp(const char* path, mode_t mode) {
  int ret;
        
  char* wpath;
 
  /* strip any trailing slash, since POSIX mkdir() doesn't accept it */
  ret = strlen(path);
  if (path[ret] == '/') {
    wpath = strdup(path);
    wpath[ret] = '\0';
    path = wpath;
  }
  if ( (ret = mkdir(path, mode)) < 0) {
    if (errno == ENOENT) {
      wpath = dirname(path);
      if (strcmp(wpath, path) == 0)
        return -1;
      fprintf(stderr, "directory %s missing, trying to create %s\n",
              path, wpath);
      wpath = strdup(wpath); /* dirname returns static storage */
      mkdirp(wpath, mode);
      free(wpath);
      return mkdir(path, mode);
    } else 
      return -1;
  } else 
    return ret;
        
}

int main (int argc, char** argv) {

  char buffer[BUFSIZ], datadir[MAXPATHLEN],
    filename[MAXPATHLEN];
  char *n; /* temporary handle for arg parsing */

  int i, fd, ch;
  int max_baks = MAX_BAKS;

  /* command line parsing */
  while ((ch = getopt(argc, argv, 
                      "chV?"
                      )) != -1) {
    switch (ch) {
    case 'c':       /* we ignore the -c arg */
      break;
    case 'V':
      printf("bksh $Name: BKSH_REL_1_7 $\n");
      printf("Compiled with:\n");
      printf("Static %d backups maximum\n", max_baks);
#ifdef FILE_BACKUP
      printf("Regular to-file backups\n");
#else
      printf("Tape-only backups\n");
#endif
      printf("Default backup dir: %s, default filename: %s\n",
             DEF_DATADIR, DEF_FILENAME);
      printf("Copyright (c) 2002-2003 The Anarcat <anarcat@anarcat.ath.cx>\n");
      exit(1);
      break;
    default:
      fprintf(stderr, "wrong argument: %c\n", ch);
      break;
    }
  }
  argc -= optind;
  argv += optind;

  /* manually parse the remaining options
   *
   * we tokenize each remaining argument into a args array that will
   * be acceptable input to getopt(3)
   */
  if (argc >= 1) {
    char *args[MAX_ARGS], *arg;
    int i, argcount = 0;
    for (i = 0; i < argc; i++) {
      /* tokenize the string */
      for (arg = argv[i]; *arg; arg++) {
        /* eat whitespace */
        while (*arg && isspace(*arg)) arg++;
        if (!*arg)
          break;

        if (argcount < MAX_ARGS) {
          args[argcount++] = arg;
          /* eat non whitespace */
          while (*arg && !isspace(*arg)) arg++;
          if (!*arg)
            break;
          *arg = 0; /* terminate string here */
        } else {
          fprintf(stderr, "maximum argument count (%d) exceeded\n", MAX_ARGS);
          break;
        }
      }
    }
    
    optind = 1;
    optreset = 1;
    /* at this point we should have an array of args properly constructed */
    while ((ch = getopt(argcount, args, 
                        "t:"
                        )) != -1) {
      if (ch == 't') {
        char* m;
     	max_baks = strtol(optarg, &m, 0);
	if (*m) {          /* oups, did not stop at the end */
           fprintf(stderr, "invalid numeric value: %s, ignoring\n", optarg);
           max_baks = MAX_BAKS;
        }
      } else {
        fprintf(stderr, "wrong argument: %c\n", ch);
      }
    }
  }

#ifdef FILE_BACKUP
  if ( (argc >= 1) &&
       ((strcmp(argv[0], "/dev/sa0") == 0) ||
        (strcmp(argv[0], "/dev/nsa0") == 0) ||
        (strcmp(argv[0], "/dev/esa0") == 0)) ) {
    strcpy(filename, argv[0]);
  } else {
    char backup_file[MAXPATHLEN];
    char *back_name = DEF_FILENAME;
    char *home      = getenv("HOME");
    char *client_hn = getenv("SSH_CLIENT");

    /* mandatory environment */
    if (home == NULL) {
      fprintf(stderr, "HOME not set, aborting\n");
      exit(1);
    }
    if (client_hn == NULL) {
      fprintf(stderr, "SSH_CLIENT not set, aborting\n");
      exit(1);
    }

    /* first space in the SSH_CLIENT string delimits hostname */
    for (n = client_hn; *n && *n != ' '; n++) {
    }
    *n = '\0';              /* end the string after the hostname */
        
    snprintf(datadir, MAXPATHLEN, "%s/%s/%s",
             home, DEF_DATADIR, client_hn);

    /* make hierarchy leading to the drop dir */
    mkdirp(datadir, 0700);

    /* we take the backup name from the first argument */
    if (argc >= 1)
      back_name = basename(argv[0]);

    snprintf(filename, MAXPATHLEN, "%s/%s",
             datadir, back_name);

    if (max_baks > 1) {
      /* rotate the files */
        
      /* 1- find last archive */
      for (i = 1; i < (max_baks-1) && 
             (access(filename, F_OK) == 0); i++) {
        snprintf(filename, MAXPATHLEN, "%s/%s.%d",
                 datadir, back_name, i);
      }
          
      if (i < (max_baks-1)) i--; /* filename i didn't exist */
          
      /* 2- rotate numerically named files */
      while (i > 1) {
        snprintf(backup_file, MAXPATHLEN, "%s/%s.%d",
                 datadir, back_name, i);
        snprintf(filename, MAXPATHLEN, "%s/%s.%d",
                 datadir, back_name, (--i));
        rename(filename, backup_file);
      }
      /* 3- rotate last file left */
      if (i == 1) {
        snprintf(backup_file, MAXPATHLEN, "%s/%s.%d",
                 datadir, back_name, i);
        snprintf(filename,  MAXPATHLEN, "%s/%s",
                 datadir, back_name);
        rename(filename, backup_file);
      }
    } else { /* only one backup file allowed, remove prior art */
      unlink(filename);
    }
  }
#else
  strcpy(filename, "/dev/nsa0");
#endif
  /* write the file */
  fd = open(filename, O_WRONLY | O_CREAT, 0400);
  if (fd < 0) {
    fprintf(stderr, "can't open file %s: %s\n",
            filename, strerror(errno));
    exit(1);
  }

  while ( (i = read(STDIN_FILENO, buffer, BUFSIZ)) > 0) {
    if (write(fd, buffer, i) < 0) {
      fprintf(stderr, "can't write to file %s: %s", 
              filename, strerror(errno));
      exit(1);
    }      
  }
  close (fd);
  printf("written to %s\n", filename);
  if (i < 0) {
    fprintf(stderr, "cant't read from stdin: %s", strerror(errno));
    exit(1);
  }
  return 0;
}
