/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* appl/simple/server/sim_server.c */
/*
 * Copyright 1989,1991 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

/*
 * Usage:
 * sample_server servername
 *
 * Simple UDP-based server application.  For demonstration.
 * This program performs no useful function.
 */

#include "krb5.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "com_err.h"

#include "simple.h"

/* for old Unixes and friends ... */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif

#define PROGNAME argv[0]

static void
usage(char *name)
{
    fprintf(stderr, "usage: %s [-p port] [-s service] [-S keytab]\n", name);
}

int
main(int argc, char *argv[])
{
    int sock, i;
    unsigned int len;
    int flags = 0;                      /* for recvfrom() */
    int on = 1;
    struct servent *serv;
    struct hostent *host;
    struct sockaddr_in s_sock;          /* server's address */
    struct sockaddr_in c_sock;          /* client's address */
    char full_hname[MAXHOSTNAMELEN];
    char *cp;
    extern char * optarg;
    int ch;

    short port = 0;             /* If user specifies port */
    krb5_keytab keytab = NULL;  /* Allow specification on command line */
    char *service = SIMPLE_SERVICE;

    krb5_error_code retval;
    krb5_data packet, message;
    unsigned char pktbuf[BUFSIZ];
    krb5_principal sprinc;
    krb5_context context;
    krb5_auth_context auth_context = NULL;
    krb5_address addr;
    krb5_ticket *ticket = NULL;

    retval = krb5_init_context(&context);
    if (retval) {
        com_err(argv[0], retval, "while initializing krb5");
        exit(1);
    }

    /*
     * Parse command line arguments
     *
     */
    while ((ch = getopt(argc, argv, "p:s:S:")) != -1) {
        switch (ch) {
        case 'p':
            port = atoi(optarg);
            break;
        case 's':
            service = optarg;
            break;
        case 'S':
            if ((retval = krb5_kt_resolve(context, optarg, &keytab))) {
                com_err(PROGNAME, retval,
                        "while resolving keytab file %s", optarg);
                exit(2);
            }
            break;

        case '?':
        default:
            usage(PROGNAME);
            exit(1);
            break;
        }
    }

    if ((retval = krb5_sname_to_principal(context, NULL, service,
                                          KRB5_NT_SRV_HST, &sprinc))) {
        com_err(PROGNAME, retval, "while generating service name %s", service);
        exit(1);
    }

    /* Set up server address */
    memset(&s_sock, 0, sizeof(s_sock));
    s_sock.sin_family = AF_INET;

    if (port == 0) {
        /* Look up service */
        if ((serv = getservbyname(SIMPLE_PORT, "udp")) == NULL) {
            fprintf(stderr, "service unknown: %s/udp\n", SIMPLE_PORT);
            exit(1);
        }
        s_sock.sin_port = serv->s_port;
    } else {
        s_sock.sin_port = htons(port);
    }

    if (gethostname(full_hname, sizeof(full_hname)) < 0) {
        perror("gethostname");
        exit(1);
    }

    if ((host = gethostbyname(full_hname)) == (struct hostent *)0) {
        fprintf(stderr, "%s: host unknown\n", full_hname);
        exit(1);
    }
    memcpy(&s_sock.sin_addr, host->h_addr, sizeof(s_sock.sin_addr));

    /* Open socket */
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("opening datagram socket");
        exit(1);
    }

    /* Let the socket be reused right away */
    (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
                      sizeof(on));

    /* Bind the socket */
    if (bind(sock, (struct sockaddr *)&s_sock, sizeof(s_sock))) {
        perror("binding datagram socket");
        exit(1);
    }

    printf("starting...\n");

#ifdef DEBUG
    printf("socket has port # %d\n", ntohs(s_sock.sin_port));
#endif

    /* GET KRB_AP_REQ MESSAGE */

    /* use "recvfrom" so we know client's address */
    len = sizeof(struct sockaddr_in);
    if ((i = recvfrom(sock, (char *)pktbuf, sizeof(pktbuf), flags,
                      (struct sockaddr *)&c_sock, &len)) < 0) {
        perror("receiving datagram");
        exit(1);
    }

    printf("Received %d bytes\n", i);
    packet.length = i;
    packet.data = (krb5_pointer) pktbuf;

    /* Check authentication info */
    if ((retval = krb5_rd_req(context, &auth_context, &packet,
                              sprinc, keytab, NULL, &ticket))) {
        com_err(PROGNAME, retval, "while reading request");
        exit(1);
    }
    if ((retval = krb5_unparse_name(context, ticket->enc_part2->client,
                                    &cp))) {
        com_err(PROGNAME, retval, "while unparsing client name");
        exit(1);
    }
    printf("Got authentication info from %s\n", cp);
    free(cp);

    /* Set foreign_addr for rd_safe() and rd_priv() */
    addr.addrtype = ADDRTYPE_INET;
    addr.length = sizeof(c_sock.sin_addr);
    addr.contents = (krb5_octet *)&c_sock.sin_addr;
    if ((retval = krb5_auth_con_setaddrs(context, auth_context,
                                         NULL, &addr))) {
        com_err(PROGNAME, retval, "while setting foreign addr");
        exit(1);
    }

    addr.addrtype = ADDRTYPE_IPPORT;
    addr.length = sizeof(c_sock.sin_port);
    addr.contents = (krb5_octet *)&c_sock.sin_port;
    if ((retval = krb5_auth_con_setports(context, auth_context,
                                         NULL, &addr))) {
        com_err(PROGNAME, retval, "while setting foreign port");
        exit(1);
    }

    /* GET KRB_MK_SAFE MESSAGE */

    /* use "recvfrom" so we know client's address */
    len = sizeof(struct sockaddr_in);
    if ((i = recvfrom(sock, (char *)pktbuf, sizeof(pktbuf), flags,
                      (struct sockaddr *)&c_sock, &len)) < 0) {
        perror("receiving datagram");
        exit(1);
    }
#ifdef DEBUG
    printf("&c_sock.sin_addr is %s\n", inet_ntoa(c_sock.sin_addr));
#endif
    printf("Received %d bytes\n", i);

    packet.length = i;
    packet.data = (krb5_pointer) pktbuf;

    if ((retval = krb5_rd_safe(context, auth_context, &packet,
                               &message, NULL))) {
        com_err(PROGNAME, retval, "while verifying SAFE message");
        exit(1);
    }
    printf("Safe message is: '%.*s'\n", (int) message.length, message.data);

    krb5_free_data_contents(context, &message);

    /* NOW GET ENCRYPTED MESSAGE */

    /* use "recvfrom" so we know client's address */
    len = sizeof(struct sockaddr_in);
    if ((i = recvfrom(sock, (char *)pktbuf, sizeof(pktbuf), flags,
                      (struct sockaddr *)&c_sock, &len)) < 0) {
        perror("receiving datagram");
        exit(1);
    }
    printf("Received %d bytes\n", i);

    packet.length = i;
    packet.data = (krb5_pointer) pktbuf;

    if ((retval = krb5_rd_priv(context, auth_context, &packet,
                               &message, NULL))) {
        com_err(PROGNAME, retval, "while verifying PRIV message");
        exit(1);
    }
    printf("Decrypted message is: '%.*s'\n", (int) message.length,
           message.data);

    krb5_auth_con_free(context, auth_context);
    krb5_free_context(context);

    exit(0);
}
