/***********************************************************
*       libsrs_alt - A simple SRS implementation           *
***********************************************************/

/* ©2004 Miles Wilton <miles@mirtol.com> */

/* License: GPL */

/* This file:

   srs.c

   srs command line tool and daemon
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>


#include "src/srs_alt.h"

/* Prototypes */

int main(int argc, char **argv, char **env);
int display_help(void);
int add_secret(char *secret);
int do_forward(FILE *fh, char *add, char *als);
int do_reverse(FILE *fh, char *add);
int start_srs(void);
int srsd(void);
void handle_signal(int signum);


/* Defs */

#define MAX_FILE_LINE_LENGTH            1024
#define MAX_SOCKET_LINE_LENGTH          1024
#define MAX_SOCKET_WAITING              8


/* Global vars */

char *alias = NULL;
int hashlength = SRS_DEFAULT_HASH_LEN;
char separator = '=';
char **secrets = NULL;
int secrets_size = 0;
int secrets_count = 0;
int mode = 0;
char *socket_name = "/tmp/srsd";

srs_t *srs;
srs_t **srsa;


/***********************************************************
    main

*/

int main(int argc, char **argv, char **env)
{
    int n, m, r;
    char *add;
    char srsbuf[SRS_MAX_ADDRESS_LENGTH+1];

    /*
       -- PROCESS COMMAND LINE --
    */
    for(n = 1; n < argc; n++)
    {
        if(argv[n][0] == '-')
        {
            // Flag
            if(argv[n][1] == '-')
                m = 2;
            else
                m = 1;

            if(strncasecmp(&argv[n][m], "address=", 8) == 0)
            {
                // Ok, savr for later
            }
            else if(strncasecmp(&argv[n][m], "alias=", 6) == 0)
            {
                if((alias = strchr(&argv[n][m+6], '@')) == NULL)
                    alias = &argv[n][m+6];
                else
                    alias++;
            }
            else if(strncasecmp(&argv[n][m], "d", 2) == 0)
            {
                if(mode != 0)
                {
                    fprintf(stderr, "ERROR: reverse, forward and d flags are mutually exclusive\n\n");
                    exit(1);
                }
                mode = -1;
            }
            else if(strncasecmp(&argv[n][m], "forward", 8) == 0)
            {
                if(mode != 0)
                {
                    fprintf(stderr, "ERROR: reverse, forward and d flags are mutually exclusive\n\n");
                    exit(1);
                }
                mode = 1;
            }
            else if(strncasecmp(&argv[n][m], "hashlength=", 11) == 0)
            {
                hashlength = atoi(&argv[n][m+11]);
                if(hashlength < 1)
                {
                    fprintf(stderr, "ERROR: Invalid hash length\n\n");
                    exit(1);
                }
            }
            else if(strncasecmp(&argv[n][m], "help", 5) == 0)
            {
                display_help();
                exit(0);
            }
            else if(strncasecmp(&argv[n][m], "reverse", 8) == 0)
            {
                if(mode != 0)
                {
                    fprintf(stderr, "ERROR: reverse, forward and d flags are mutually exclusive\n\n");
                    exit(1);
                }
                mode = 2;
            }
            else if(strncasecmp(&argv[n][m], "secret=", 7) == 0)
            {
                if(add_secret(&argv[n][m+7]))
                {
                    fprintf(stderr, "ERROR: Out of memory\n\n");
                    exit(1);
                }
            }
            else if(strncasecmp(&argv[n][m], "secretfile=", 11) == 0)
            {
                FILE *fh;
                char buf[MAX_FILE_LINE_LENGTH];
                int l;

                if((fh = fopen(&argv[n][m+11], "r")) == NULL)
                {
                    fprintf(stderr, "ERROR: Could not open file '%s'\n\n", &argv[n][m+11]);
                    exit(1);
                }

                // Read in file and add secrets
                while(fgets(buf, MAX_FILE_LINE_LENGTH, fh))
                {
                    l = strnlen(buf, MAX_FILE_LINE_LENGTH);
                    if(buf[l-1] == '\n')
                    {
                        buf[l-1] = 0;
                        l--;
                    }

                    if(l > 0)
                    {
                        if(add_secret(strdup(buf)))
                        {
                            fprintf(stderr, "ERROR: Out of memory\n\n");
                            exit(1);
                        }
                    }
                }

                fclose(fh);
            }
            else if(strncasecmp(&argv[n][m], "separator=", 10) == 0)
            {
                char c = argv[n][m+10];
                if(c == '+' || c == '-' || c == '=')
                    separator = c;
                else
                {
                    fprintf(stderr, "ERROR: Invalid separtor '%c'\n\n", c);
                    exit(1);
                }
            }
            else if(strncasecmp(&argv[n][m], "socket=", 7) == 0)
            {
                if(mode != -1)
                {
                    fprintf(stderr, "ERROR: socket must come after d\n\n");
                    exit(1);
                }
                socket_name = &argv[n][m+7];
            }
            else
            {
                fprintf(stderr, "ERROR: Unknown flag '%s'\n\n", argv[n]);
                exit(1);
            }
        }
    }


    /*
       -- CHECK SETTINGS --
    */
    if(mode == 1 && alias == NULL)
    {
        fprintf(stderr, "ERROR: You must specify an alias\n\n");
        exit(1);
    }

    if(secrets_count == 0)
    {
        fprintf(stderr, "ERROR: No secret specified\n\n");
        exit(1);
    }


    /*
       -- SET UP SRS HANDLES --
    */
    start_srs();


    /*
       -- PERFORM TASK --
    */
    if(mode == 1 || mode == 2)
    {
        // Command line call
        n = 1;
        for(n = 1; n < argc; n++)
        {
            if(argv[n][0] == '-')
            {
                // Flag
                if(argv[n][1] == '-')
                    m = 2;
                else
                    m = 1;

                // If not address flag, go on to next param
                if(strncasecmp(&argv[n][m], "address=", 8) != 0)
                    continue;

                // Set pointer
                add = &argv[n][m+8];
            }
            else
                add = argv[n];

            // Do it
            if(mode == 1)
                do_forward(stdout, add, alias);
            else
                do_reverse(stdout, add);
        }
    }
    else if(mode == -1)
        // Daemonise
        srsd();
    else
    {
        printf("ERROR: You must specify one of forward, reverse or d\n\n");
        exit(1);
    }

    // Should never get here!
    return -1;
}


/***********************************************************
    display_help - show help text

*/
int display_help(void)
{
    printf("srs - libsrs_alt library http://srs.mirtol.com/\n(C)2004 Miles Wilton\n\n");
    printf("Syntax: srs <flags> <address>+\n\n");
    printf("    Actions\n");
    printf("        -d               Run daemon\n");
    printf("        --forward        Perform SRS forward on address\n");
    printf("        --reverse        Perform SRS reverse on address\n");
    printf("\n    Options\n");
    printf("        --address=       Different way of specifying addresses to process\n");
    printf("        --alias=         Alias domain (or address) for SRS forward\n");
    printf("        --hashlength=    Characters in hash length\n");
    printf("        --help           Display this help\n");
    printf("        --secret=        SRS secret\n");
    printf("        --secretfile=    Read file for SRS secret(s)\n");
    printf("        --separator=     SRS separator, must be + - or =\n");
    printf("        --socket=        Specify SRS socket for daemon\n");
    printf("\nSyntax compatible with perl implementation Mail::SRS\nMultiple secrets and addresses supported, can also use - instead of --\n\n");

    return 0;
}


/***********************************************************
    add_secret

*/

int add_secret(char *secret)
{
    // Check array space
    if(secrets == NULL)
    {
        secrets = (char **)malloc(32 * sizeof(char *));
        if(secrets == NULL)
            return 1;
        secrets_size = 32;
    }

    if(secrets_size == secrets_count)
    {
        char **ra;

        ra = (char **)realloc(secrets, (secrets_count + 32) * sizeof(char *));
        if(ra == NULL)
            return 1;
        secrets = (char **)ra;
        secrets_count += 32;
    }

    // Add to array
    secrets[secrets_count++] = secret;

    return 0;
}


/***********************************************************
    do_forward

*/

int do_forward(FILE *fh, char *add, char *als)
{
    char srsbuf[SRS_MAX_ADDRESS_LENGTH];
    int r;

    // SRS Forward
    if((r = srs_forward(srs, add, als, srsbuf, SRS_MAX_ADDRESS_LENGTH)) & SRS_RESULT_FAIL)
        fprintf(fh, "ERROR: Address '%s' failed - %s\n", add, srs_geterrormsg(r));
    else
        fprintf(fh, "%s\n", srsbuf);

    return 0;
}


/***********************************************************
    do_reverse

*/

int do_reverse(FILE *fh, char *add)
{
    char srsbuf[SRS_MAX_ADDRESS_LENGTH];
    int m, r;

    // SRS Reverse
    for(m = 0; m < secrets_count; m++)
    {
        if((r = srs_reverse(srsa[m], add, srsbuf, SRS_MAX_ADDRESS_LENGTH)) & SRS_RESULT_FAIL)
        {
            if(r == SRS_RESULT_NOTSRS || r == SRS_RESULT_BADSRS || r == SRS_RESULT_HASHTOOSHORT
                    || r == SRS_RESULT_BADTIMESTAMP || r == SRS_RESULT_BADTIMESTAMP)
                m = secrets_count;
        }
        else
            break;
    }

    if(m >= secrets_count)
        fprintf(fh, "ERROR: Could not validate address '%s' - %s\n", add, srs_geterrormsg(r));
    else
    {
        fprintf(fh, "%s\n", srsbuf);
        if(r == SRS_RESULT_CASEBROKEN)
            fprintf(fh, "WARNING: Case broken on hash in address '%s'\n", add);
    }
}


/***********************************************************
    start_srs

*/

int start_srs(void)
{
    int n;

    if(mode == 1)
    {
        // Only one secret required, even if multiple secrets given
        if((srs = srs_open(secrets[0], strnlen(secrets[0], SRS_MAX_SECRET_LENGTH), 0, hashlength, hashlength)) == NULL)
        {
            fprintf(stderr, "ERROR: SRS initialisation failed\n\n");
            exit(1);
        }
    }
    else
    {
        // For reverse or daemon, all secrets required
        if((srsa = (srs_t **)malloc(secrets_count * sizeof(srs_t *))) == NULL)
        {
            fprintf(stderr, "ERROR: Out of memory\n\n");
            exit(1);
        }

        for(n = 0; n < secrets_count; n++)
            if((srsa[n] = srs_open(secrets[n], strnlen(secrets[n], SRS_MAX_SECRET_LENGTH), 0, hashlength, hashlength)) == NULL)
            {
                fprintf(stderr, "ERROR: SRS initialisation failed\n\n");
                exit(1);
            }

        srs = srsa[0];
    }

    return 0;
}


/***********************************************************
    srsd

*/

int srsd(void)
{
    int n, children = 0;

    if((n = fork()) == 0)
    {
        // Child - open a socket and wait...

        int s, sl, ns, csl;
        struct sockaddr_un *sa, cs;

        if((s = socket(AF_UNIX, SOCK_STREAM, 0)) == 0)
        {
            printf("ERROR: Could not create socket\n\n");
            exit(1);
        }

        // Setup socket bind
        sl = sizeof(sa->sun_family) + strlen(socket_name);
        if((sa = (struct sockaddr_un *)malloc(sl)) == NULL)
        {
            printf("ERROR: Out of memory\n\n");
            exit(1);
        }
        
        sa->sun_family = AF_UNIX;
        strcpy(sa->sun_path, socket_name);

        // Delete any file that might obstruct socket
        unlink(socket_name);

        // Bind to socket
        if(bind(s, (struct sockaddr *)sa, sl) != 0)
        {
            printf("ERROR: Could not bind to socket\n\n");
            exit(1);
        }

        // Listen for connections
        if(listen(s, MAX_SOCKET_WAITING) != 0)
        {
            printf("ERROR: Could not listen to socket\n\n");
            exit(1);
        }

        // Setup SIGCHLD handler
        signal(SIGCHLD, handle_signal);

        // Accept connections
        csl = sizeof(cs);

        while((ns = accept(s, (struct sockaddr *)&cs, &csl)) > 0)
        {
            // Start thread to process request
            if(fork() == 0)
            {
                FILE *sr;
                char buf[MAX_SOCKET_LINE_LENGTH];
                int nn, nl;

                // Open for reading
                if((sr = fdopen(ns, "r+")) == NULL)
                {
                    printf("ERROR: Failed to open descriptor\n");

                    // Close socket and continue
                    close(ns);
                    continue;
                }

                // Wait for command
                fgets(buf, MAX_SOCKET_LINE_LENGTH, sr);
                nl = strnlen(buf, MAX_SOCKET_LINE_LENGTH);

                // Remove \n from end of string
                {
                if(buf[nl-1] == '\n')
                    buf[nl-1] = 0;
                    nl--;
                }

                // Process command
                if(strncasecmp(buf, "forward ", 8) == 0)
                {
                    for(nn = 8; nn < nl; nn++)
                        if(buf[nn] == ' ')
                            break;
                    if(nn >= nl)
                        fprintf(sr, "ERROR\n");
                    else
                    {
                        buf[nn++] = 0;
                        do_forward(sr, &buf[n+8], &buf[nn]);
                    }
                }
                else if(strncasecmp(buf, "reverse ", 8) == 0)
                    do_reverse(sr, &buf[n+8]);
                else
                    fprintf(sr, "ERROR\n");

                // Close stream
                fclose(sr);

                // Close socket
                close(ns);

                // End thread
                exit(0);
            }
            else
            {
                // Close socket handle as far as parent is concerned
                close(ns);
            }

            // Reset csl
            csl = sizeof(cs);
        }
      
        // Close socket
        close(s);

        // Close handles and free memory
        for(n = 0; n < secrets_count; n++)
            srs_close(srsa[n]);
        free(srsa);
        free(secrets);

        exit(0);
    }
    else
    {
        // Parent - quits peacably if successful
        printf("srs - libsrs_alt library http://srs.mirtol.com/\n©2004 Miles Wilton\n\n");
        if(n == -1)
        {
            printf("ERROR: fork() failed\n");
            exit(1);
        }

        printf("Daemon started\n");
        exit(0);
    }
}


/***********************************************************
    srsd

*/

void handle_signal(int signum)
{
    if(signum == SIGCHLD)
        wait(NULL);
}



syntax highlighted by Code2HTML, v. 0.9.1