/*
 * nndb --- a C delivery agent for nndb.
 * Copyright (C) 1996 Joe Hildebrand

 * Author:   Joe Hildebrand <hildjj@fuentez.com>
 * Keywords: news mail

 * nndb 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, or (at your option)
 * any later version.

 * nndb 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 GNU Emacs; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_TIME_H
#include <time.h>
#endif

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

static int verbose = 0;


#define CHECK(cond)                             \
if (! (cond)) {                                 \
    log_error(#cond,                            \
              errno,                            \
              __FILE__,                         \
              __LINE__);                        \
    exit(1);                                    \
}

#define CHECK_RET(cond,ret)                     \
if (! (cond)) {                                 \
    log_error(#cond,                            \
              errno,                            \
              __FILE__,                         \
              __LINE__);                        \
    return(ret);                                \
}

void
log_error(char *cond,
          int  errno,
          char *file,
          int  linno)
{
    char buf[512];
#ifndef HAVE_STRERROR
    extern char *sys_errlist[];
#endif
    
#ifdef HAVE_ISATTY
    if (! isatty(fileno(stderr)))
    {
        return;
    }
#endif    

    sprintf(buf,
            "Error checking %s: '%s' in file %s at line %d\n",
            cond,
#ifdef HAVE_STRERROR
            strerror(errno),
#else
            sys_errlist[errno],
#endif      
            file,
            linno);


    fprintf(stderr, buf);
}

void
put_line(int  f,
         char *line)
{
    int pos = 0;
    int len;
    int num_left = strlen(line);

    while (num_left > 0) {
        len = write(f, line + pos, num_left);
        CHECK(len != -1);

        pos      += len;
        num_left -= len;
    }
}

int
get_line(int    f,
         char   *buf,
         size_t b_size) 
{
    int status = -1;

    memset(buf, 0, b_size);
    
    while (status == -1)
    {
        status = read(f, buf, b_size - 1);
        if (status == -1)
        {
            
            if (errno != EINTR)
            {
                CHECK_RET(0, -1);
            }
        
            sleep(1);
        }
    }
    
    if (verbose)
    {
        fprintf(stderr, buf);
    }

    return status;
}

int
init_sock(int                *s,
          unsigned long      ip,
          unsigned short int port)
{
    struct sockaddr_in addr;
    struct protoent   *proto    = NULL;
    int               connected = 0;
    int               retries   = 10;
    int                status;

    *s = -1;

    proto = getprotobyname("tcp");
    CHECK_RET(proto, 0);
    
    *s = socket(AF_INET, SOCK_STREAM, proto->p_proto);
    CHECK_RET((*s != -1), 0);
  
    addr.sin_family      = AF_INET;
    addr.sin_port        = port;
    addr.sin_addr.s_addr = ip;
  
    status = connect(*s, (struct sockaddr *) &addr, sizeof(addr));
    CHECK_RET((status != -1), 0);

    return(1);
}

void
usage()
{
    fprintf(stderr, "usage: nndel [-v] [-g group] [-h host] [-p port]"
            "[-m maildir]\n");
    
    exit(64);
}

int
write_maildir(char *mail_dir,
              char *file_name)
{
    char   buf[1024];
    char   dir_buf[256];
    struct stat stats;
    int    status    = 0;
    int    fd        = -1;
    int    retries   = 10;
    int    num_bytes = 0;
    int    last_verbose;
    
#ifndef HAVE_GETHOSTNAME
    char   *hostname = NULL;
    struct utsname uts;
    
    status = uname(&uts);
    CHECK(status != -1);
    
    hostname = uts.nodename;
#else
    char hostname[256];
    
    status = gethostname (hostname, sizeof(hostname) - 1);
    CHECK(status != -1);
#endif
    
    if (! mail_dir)
    {
        struct passwd *pass = getpwuid(getuid());
        sprintf(dir_buf, "%s/Mail/incoming", pass->pw_dir);
        mail_dir = strdup(dir_buf);
    }

    /* If the MAILDIR directory does not exist, and can't be created,
       deliver to the user's home directory.  If the user can't write
       to her home directory, she loses */
    
    status = chdir(mail_dir);

    if (status == -1)
    {
        status = mkdir(mail_dir, 0700);
        if ((status == -1) ||
            (chdir(mail_dir) == -1))
        {
            struct passwd *pass = getpwuid(getuid());
            sprintf(dir_buf, "%s", pass->pw_dir);
            mail_dir = dir_buf;

            status = chdir(mail_dir);
            CHECK(status != -1);
        }
    }

    /* Check to make sure that mail_dir/tmp and mail_dir/new exist.
       If not, create them. */
    status = stat("tmp", &stats);
    if (status)
    {
        status = mkdir("tmp", 0700);
        CHECK(status != -1);
    }
    
    status = stat("new", &stats);
    if (status)
    {
        status = mkdir("new", 0700);
        CHECK(status != -1);
    }

    /* This goes into a loop, in case there is another machine
       attached to your NFS server with the same hostname, that has
       a program running with the same pid, at this same second in
       time.  Either that, or you rebooted your machine and received a
       mail, all within one second from the last attempted delivery.
       In other words, this is bloddy unlikely, but the maildir spec
       says to do it this way. */
    
    fd      = -1;
    retries = 10;
    last_verbose = verbose;
    verbose = 0;
    
    while ((fd == -1) && retries > 0)
    {
        sprintf(file_name, "tmp/%d.%d.%s",
                time(NULL),
                getpid(),
                hostname);
        
        fd = open(file_name, O_CREAT | O_WRONLY | O_EXCL, 0600);
        if ((fd == -1) && (errno = EEXIST))
        {
            sleep(2);
        }
        else
        {
            break;
        }
        
        retries--;
    }
    
    CHECK(fd != -1);

    while ((num_bytes = get_line(fileno(stdin),
                                 buf,
                                 sizeof(buf))) > 0)
    {
        put_line(fd, buf);
    }

    status = fsync(fd);
    CHECK(status != -1);
    
    status = close(fd);
    CHECK(status != -1);

    verbose = last_verbose;
}

int
write_sock(int  sock,
           char *file_name,
           char *group)
{
    char buf[1024];
    FILE *f;
    
    get_line(sock, buf, sizeof(buf));

    if (group) {
        put_line(sock, "GROUP ");
        put_line(sock, group);
        put_line(sock, "\r\n");
        
        get_line(sock, buf, sizeof(buf));
        put_line(sock, "POST\r\n");
        get_line(sock, buf, sizeof(buf));
    }
    else {
        put_line(sock, "POSTRULE\r\n");
        get_line(sock, buf, sizeof(buf));
    }

    f = fopen(file_name, "r");
    CHECK_RET(f, 0);
    
    while(fgets(buf, sizeof(buf), f)) {
        put_line(sock, buf);
    }

    fclose(f);
    
    put_line(sock, ".\r\n");
    get_line(sock, buf, sizeof(buf));

    put_line(sock, "QUIT\r\n");

    return(1);
}

void
unlink_maildir(char *file_name)
{
    int status = unlink(file_name);
    CHECK(status != -1);
}

void
move_maildir(char *file_name)
{
    int  status;
    char *new_name = strdup(file_name);

    /* this is kind of a hack, but it will work.  avert your eyes. */
    new_name[0] = 'n';
    new_name[1] = 'e';
    new_name[2] = 'w';
    
    status = link(file_name, new_name);
    CHECK(status != -1);

    unlink_maildir(file_name);
}

main(int argc,
     char *argv[])
{
    int                sock  = -1;
    unsigned long      ip    = 0;
    unsigned short int port  = 9000;
    char              *group = NULL;
    struct hostent    *hp    = NULL;
    char               c;
    char               file_name[1024];
    char               *maildir = getenv("MAILDIR");
    extern char        *optarg;

    ip = inet_addr("127.0.0.1");

    while ((c = getopt(argc, argv, "vp:h:g:m:")) != EOF)
    {
        switch (c) 
        {
        case 'p':
            port = atoi(optarg);
            break;
        case 'h':
            if ((ip = inet_addr(optarg)) == -1) {
                hp = gethostbyname(optarg);
                CHECK(hp);

                ip = * (unsigned long *) hp->h_addr;
            }
            break;
        case 'm':
            maildir = optarg;
            break;
        case 'v':
            verbose = 1;
            break;
        case 'g':
            group = optarg;
            break;

        default:
            usage();
        }
    }

    if (verbose)
    {
        struct in_addr in;
        in.s_addr = ip;
        
        fprintf(stderr, "nndel version %s\n",  VERSION);
        fprintf(stderr, "   maildir = %s\n",   maildir);
        fprintf(stderr, "   host ip = %s\n",   inet_ntoa(in));
        fprintf(stderr, "   port    = %d\n\n", port);
    }
    
    write_maildir(maildir, file_name);
    
    /* If we can talk to the server, try to write the message to it.
       If that works, unlink file in maildir/tmp.
       If any of it fails, move the message from tmp to new. */
    if (init_sock(&sock, ip, port) &&
        write_sock(sock, file_name, group))
    {
        unlink_maildir(file_name);
    }
    else
    {
        move_maildir(file_name);
    }
    
    exit(0);
}
