
/* dyndnsupdate --- dyndnsupdate.c --- Jun 17 2003
 * Copyright (c) 2002 Fredrik "xzabite" Haglund (xzabite@xzabite.org)
 * http://xzabite.org
 *
 * 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 (at your option) 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
//#include <netinet/in.h>
//#include <sys/socket.h>
//#include <malloc.h>
#include <syslog.h>
#include <stdarg.h>

#include "dyndnsupdate.h"
#include "base64encode.c"
#include "interface.c"
#include "cachefile.c"

int main(int argc, char *argv[])
{
    int req = 0;
    char b64user[MAXLEN];
    int a;
    char user[MAXLEN];
    char ip[MAXLEN];
    char wildcard[MAXLEN] = "NOCHG\0";
    char mxhost[MAXLEN] = "\0";
    char hostname[MAXLEN] = "\0";
    int resolvip = 0;
    char backmx[MAXLEN] = "NO\0";
    char offline[MAXLEN] = "NO\0";
    char interface[MAXLEN] = "\0";
    char systemt[MAXLEN] = "dyndns\0";
   
    int ret;
	
    /* determine how we are called */
    exec_name = strrchr(argv[0], '/'); 
    !exec_name ? exec_name = argv[0] : ++exec_name;

    if (argc > 1) {
	for ( a = 1 ; a < argc ; a++ ) {
	    if (strcomp(argv[a], "--version") ) {
		printf("dyndnsupdate %s - Copyright (c) 2001 Fredrik \"xzabite\" Haglund\n",VERSION);
		printf("xzabite@xzabite.org - http://xzabite.org\n\n");
		exit(0);
	    }
	    if ( strcomp(argv[a], "--help") ) {
		show_help();
		exit(0);
	    }
	    if ( (strcomp(argv[a], "-f") ) || (strcomp(argv[a], "--force") ) ) {
		force = 1;
	    }
	    if ( strcomp(argv[a], "--credits") ) {
		printf("AUTHOR\n");
		printf("   Fredrik \"xzabite\" Haglund <xzabite@xzabite.org>\n");
		printf("CONTRIBUTORS\n");
		printf("   Tommy \"yxol\" Rohde <yxol@fetburk.nu>\n");
		printf("       For helping me with testing.\n");
		printf("   Magnus Svensson <magnus-76@telia.com>\n");
		printf("       For helping me out with some C/C++ problems.\n");
                printf("   Folke Ashberg <folke@asberg.de>\n");
	        printf("       For some hacks to the code.\n");
	        printf("   Lars Wessels <lars@nordsee.inka.de>\n");
	        printf("       For some additional functions to the program.\n");
                printf("   Volker Schmidt <volkomat@gmx.net>\n");
	        printf("       For making the program compatible to Solaris.\n");
	        printf("\n");
		exit(0);
	    }
	    if ( (strcomp(argv[a], "-w") ) || (strcomp(argv[a], "--wildcard") ) ) {
		sprintf(wildcard,"ON");
	    }
	    if ( (strcomp(argv[a], "-s") ) || (strcomp(argv[a], "--system") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(systemt,argv[a+1]);
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		} 
	    }
	    if ( (strcomp(argv[a], "-u") ) || (strcomp(argv[a], "--user") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(user,argv[a+1]);
		    req++;
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		}
	    }
	    if ( (strcomp(argv[a], "-r") ) || (strcomp(argv[a], "--resolv-ip") ) ) {
		resolvip = 1;
		req++;
	    }
	    if ( (strcomp(argv[a], "-i") ) || (strcomp(argv[a], "--interface") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(interface,argv[a+1]);
		    resolvip = 2;
		    req++;
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		}
	    }
	    if ( (strcomp(argv[a], "-m") ) || (strcomp(argv[a], "--mx") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(mxhost,argv[a+1]);
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		}
	    }
	    if ( (strcomp(argv[a], "-h") ) || (strcomp(argv[a], "--host") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(hostname,argv[a+1]);
		    req++;
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		}
	    }
	    if ( (strcomp(argv[a], "-b") ) || (strcomp(argv[a], "--backmx") ) ) {
		sprintf(backmx,"YES");
	    }
	    if ( (strcomp(argv[a], "-o") ) || (strcomp(argv[a], "--offline") ) ) {
		sprintf(offline,"YES");
	    }
	    if ( (strcomp(argv[a], "-a") ) || (strcomp(argv[a], "--address") ) ) {
		if (argc >= a+2) { 
		    if ( check4options(argv[a+1]) ) print_error("option requires an valid argument:", argv[a]);
		    sprintf(ip,argv[a+1]);
		    req++;
		} else {
		    print_error("option requires an valid argument:", argv[a]);
		}
	    }
	    if ( (strcomp(argv[a], "-l") ) || (strcomp(argv[a], "--syslog") ) ) {
		log++;
	    }
	    if ( (strcomp(argv[a], "-q") ) || (strcomp(argv[a], "--quiet") ) ) {
		quiet++;
		}
	    if ( (strcomp(argv[a], "-d") ) || (strcomp(argv[a], "--debug") ) ) {
		debug++;
	    }
	}
    }

    if (req < 3) print_error("you haven't supplied all required options!","");
    if (req > 3) print_error("use only one of the options -a or -i or -r !",""); 

    if (log)
       openlog(exec_name, LOG_NDELAY|LOG_PID, SYSLOG_FACILITY);

    if (resolvip == 1) 
      ipcheck(ip);

    if (resolvip == 2) {
	sprintf(ip,ipcheckif(interface));
	if (strstr(ip, "Unknown") != NULL) {
	    print_msg(LOG_ERR, "failed to resolve ip from interface:", interface);
	    exit(1);
	}
    }    

    if (access("/var/dyndnsupdate", F_OK) == -1) {
       print_msg(LOG_ERR, "directory /var/dyndnsupdate does not exists, skipping update.\n");
       exit(1);
    }
   
    if ((check_ipcache(ip) != 0) || (force == 1)) {
      base64encode(user, b64user);    
      init_socket(DYNDNSSERVER);
      print_msg(LOG_INFO, "Connecting to %s...", DYNDNSSERVER);
      connect_socket();
      print_msg(LOG_INFO, "succeeded!\n");
      ret=update_dyndns(b64user, ip, wildcard, mxhost, hostname, backmx, offline, systemt);
      close_socket();
      save_ipcache(ip);
      if (log) closelog();
      if (ret) return 0; // ok
      else return 1; // error
    } else {
      print_msg(LOG_INFO, "Your ipaddress is not changed, skipping update.\n");
      return 0;
    }
}


int strcomp(char *str1, char *str2)
{
    int length = 10;
    int len = 0;
    int a = 0;

    if (strlen(str1) < strlen(str2)) length = strlen(str2);
    if (strlen(str1) > strlen(str2)) length = strlen(str1);
    if (strlen(str1) == strlen(str2)) length = strlen(str2);

    for (a = 0 ; a < length ; a++) {
	if (str1[a] == str2[a]) len++; 
    }
    if (len == length) {
	return 1; 
    } else {
	return 0;
    }
}

void print_error(char *message, char *option)
{
    printf("%s: %s %s\n", exec_name ,message, option);
    printf("Try '%s --help' for more information\n", exec_name);
    exit(0);
}


int check4options(char *str)
{
    int ret = 0;
    if (strcomp(str, "-a") ) ret = 1;
    if (strcomp(str, "-b") ) ret = 1;
    if (strcomp(str, "-f") ) ret = 1;
    if (strcomp(str, "-h") ) ret = 1;
    if (strcomp(str, "-i") ) ret = 1;
    if (strcomp(str, "-o") ) ret = 1;
    if (strcomp(str, "-m") ) ret = 1;
    if (strcomp(str, "-r") ) ret = 1;
    if (strcomp(str, "-u") ) ret = 1;
    if (strcomp(str, "-w") ) ret = 1;
    if (strcomp(str, "-d") ) ret = 1;
    if (strcomp(str, "-l") ) ret = 1;
    if (strcomp(str, "-q") ) ret = 1;
    if (strcomp(str, "--address") ) ret = 1;
    if (strcomp(str, "--backmx") ) ret = 1;
    if (strcomp(str, "--force") ) ret = 1;
    if (strcomp(str, "--host") ) ret = 1;
    if (strcomp(str, "--mx") ) ret = 1;
    if (strcomp(str, "--interface") ) ret = 1;
    if (strcomp(str, "--offline") ) ret = 1;
    if (strcomp(str, "--resolv") ) ret = 1;
    if (strcomp(str, "--user") ) ret = 1;
    if (strcomp(str, "--wildcard") ) ret = 1;
    if (strcomp(str, "--help") ) ret = 1;
    if (strcomp(str, "--credits") ) ret = 1;
    if (strcomp(str, "--version") ) ret = 1;
    if (strcomp(str, "--syslog") ) ret = 1;
    if (strcomp(str, "--quiet") ) ret = 1;
    if (strcomp(str, "--debug") ) ret = 1;
    return ret;
}

void close_socket()
{
    close(sockfd); 
}


void connect_socket()
{
    if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) == -1) {
	perror("connect");
	exit(1);
    }   
}


void init_socket(char server[MAXLEN])
{
    if ((he=gethostbyname(server)) == NULL) {   
      perror("gethostbyname");
	exit(1);
    }
    close(sockfd);       
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
	perror("socket");
	exit(1);
    }         
    address.sin_family = PF_INET;      
    address.sin_port = htons(PORT);    
    address.sin_addr = *((struct in_addr *)he->h_addr);
    memset(&(address.sin_zero),0,sizeof(address.sin_zero));   
    if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) == -1) {
	perror("connect");
	exit(1);
    }
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
	perror("socket");
	exit(1);
    }
}

void ipcheck(char *ipaddress)
{
    char *ipaddr, *q, *send_msg, *get_msg;

    print_msg(LOG_INFO, "Resolving your ip address...");

    init_socket(CHECKIPSERVER);
    connect_socket();

    send_msg = (char *)xmalloc(MAXDATASIZE);
    sprintf(send_msg,
		"GET / HTTP/1.1\r\n"
		"Host: %s\r\n"
		"User-Agent: dyndnsupdate %s - http://xzabite.org\r\n"
		"Connection: close\r\n"
		"Pragma: no-cache\r\n"
		"\r\n",
		CHECKIPSERVER, VERSION); 

    print_msg(LOG_DEBUG, "asking %s:\n%s", CHECKIPSERVER, send_msg); 
    send_func(send_msg);
    free(send_msg);

    get_msg = (char *)xmalloc(MAXDATASIZE);
    if((read(sockfd, get_msg, MAXDATASIZE)) == -1) {
	perror("read");
	exit(1);
    }
    sleep(1);
    print_msg(LOG_DEBUG, "reply from %s:\n%s", CHECKIPSERVER, get_msg);
    ipaddr = strstr(get_msg, "Address: ")+9;
    q = strchr(ipaddr, '\n'); *q = '\0';
    print_msg(LOG_INFO, "%s...Done!\n", ipaddr);
    sprintf(ipaddress, ipaddr);
    free(get_msg);
    close_socket();
}


void show_help()
{
    printf("Usage: %s [options]\n", exec_name);
    printf("Options are:\n");
    printf("   -a, --address <ip address>  IP address (Required or -r or -i).\n");
    printf("   -b, --backmx                Set backup mx ON, default is OFF.\n");
    printf("   -d, --debug                 Show communication with server.\n");
    printf("   -f, --force                 Force update when ipaddress is not changed.\n");
    printf("   -h, --host <host>,[host]    Host/hostnames (Required).\n");
    printf("   -i, --interface <interface> Network device to grab IP address from\n");
    printf("                               (Required or -a or -r).\n");
    printf("   -l, --syslog                Log all messages to syslog.\n");
    printf("   -m, --mx <mail exchange>    Hostname of your mail exchange.\n");
    printf("   -o, --offline               Set host offline.\n");
    printf("   -q, --quiet                 Don't print any status/error messages.\n");
    printf("   -r, --resolv                Resolv your IP address automaticly\n");
    printf("                               (Required or -a or -i).\n");
    printf("   -s, --system [dyndns|statdns|custom]\n");
    printf("                               Use only one of dyndns, statdns or custom\n");
    printf("                               (Without the -s/--system default is \"dynamic\").\n");
    printf("   -u, --user <user>:<passw>   User ID and Password (Required).\n");
    printf("   -w, --wildcard              Set your domain for wildcard aliases ON,\n");
    printf("                               defalut is OFF.\n");
    printf("       --help                  displays this help.\n");
    printf("       --version               displays version information.\n");
    printf("       --credits               displays credits.\n\n");
}


int check_error(char *http_response)
{
    int ret=0;
    if((strstr(http_response, "nochg") != NULL)) {
	print_msg(LOG_WARNING,"No changes, update considered abusive\n");	
	ret=1;
    }
    else if((strstr(http_response, "good") != NULL)) {
	print_msg(LOG_INFO,"Update good and successful, IP updated\n");
	ret=1;
    }
    else if((strstr(http_response, "badauth") != NULL)) {
	print_msg(LOG_ERR,"Bad authorization (username or password)\n");
	ret=0;
    }
    else if((strstr(http_response, "badsys") != NULL)) {
	print_msg(LOG_ERR,"The system parameter given was not valid\n");
	ret=0;
    }
    else if((strstr(http_response, "badagent") != NULL)) {
	print_msg(LOG_ERR,"The useragent your client sent has been blocked at the access level\n");
	ret=0;
    }
    else if((strstr(http_response, "notfqdn") != NULL)) {
	print_msg(LOG_ERR,"A Fully-Qualified Domain Name was not provided\n");
	ret=0;
    }
    else if((strstr(http_response, "nohost") != NULL)) {
	print_msg(LOG_ERR,"The hostname specified does not exists\n");
	ret=0;
    }
    else if((strstr(http_response, "!donator") != NULL)) {
	print_msg(LOG_ERR,"The offline setting was set, when the user is not a donator\n");
	ret=0;
    }
    else if((strstr(http_response, "!yours") != NULL)) {
	print_msg(LOG_ERR,"The hostname specified exists, but not under the username currently being used\n");
	ret=0;
    }
    else if((strstr(http_response, "!active") != NULL)) {
	print_msg(LOG_ERR,"The hostname specified is in a Custom DNS domain witch has not yet been activated\n");
	ret=0;
    }
    else if((strstr(http_response, "abuse") != NULL)) {
	print_msg(LOG_ERR,"The hostname spcified is blocked for abuse; contact support to be unblocked\n");
	ret=0;
    }
    else if((strstr(http_response, "numhost") != NULL)) {
	print_msg(LOG_ERR,"Too many or to few hosts found\n");
	ret=0;
    }
    else if((strstr(http_response, "dnserr") != NULL)) {
	print_msg(LOG_ERR,"DNS error encountered\n");
	ret=0;
    }
    else if((strstr(http_response, "911") != NULL)) {
	print_msg(LOG_ERR,"Shutdown until notified otherwise via http://www.dyndns.org/status.shtml\n");
	ret=0;
    }
    else {
	/* unknown */
	print_msg(LOG_ERR,"Server sent unknown status message.\n");
	ret=0;
    }
    return ret;
}


int update_dyndns(char *user, char *ip, char *wildcard, char *mxhost,
		  char *hostname, char *backmx, char *offline, char *systemt)
{
    char *data, *send_msg, *get_msg;
    int got, rc;

    send_msg = (char *)xmalloc(MAXDATASIZE);
    sprintf(send_msg,
		"GET /nic/update?system=%s&hostname=%s&myip=%s&wildcard=%s&backmx=%s&offline=%s&mx=%s HTTP/1.1\r\n"
		"Host: %s\r\n"
		"Authorization: Basic %s\r\n"
		"User-Agent: dyndnsupdate %s - http://xzabite.org\r\n"
		"Connection: close\r\n"
		"Pragma: no-cache\r\n"
		"\r\n"
		,systemt,hostname,ip,wildcard,backmx,offline,mxhost,DYNDNSSERVER,user,VERSION);

    print_msg(LOG_DEBUG, "asking %s:\n%s", DYNDNSSERVER, send_msg);
    send_func(send_msg);
    free(send_msg);

    /* since members.dyndns.org sends different small
     * packets, we have to connect all together */
    get_msg = (char*)xmalloc(MAXDATASIZE);
    data = (char*)xmalloc(MAXDATASIZE);
    while ((got=read(sockfd, data, MAXDATASIZE-1)) > 0) {
	data[got]='\0';
	if ((strlen(data) + strlen(get_msg)) < MAXDATASIZE-1) {
	    strcat(get_msg, data);
	}
    }
    print_msg(LOG_DEBUG, "reply from %s:\n%s\n", DYNDNSSERVER, get_msg);
    free(data);
    rc = check_error(get_msg); // 1 is OK, 0 is error
    free(get_msg);
    return rc;
}


void send_func(const char *send_msg_to_server) 
{
    if((send(sockfd, send_msg_to_server, strlen(send_msg_to_server), 0)) == -1) {
	perror("send");
	exit(1);
    }
}


void print_msg(int level, const char *fmt,...)
{
    static char msgbuf[MAXLEN], logbuf[MAXLEN];
    va_list args;

    memset(msgbuf, 0, sizeof(msgbuf));
    switch (level) {
        case LOG_DEBUG:
            if (!debug) return;
            strcat(msgbuf, "DEBUG: ");
            break;
        case LOG_INFO:
        case LOG_NOTICE:
            break;
        case LOG_WARNING:
            strcat(msgbuf, "WARNING: ");
	    break;
        case LOG_EMERG:
        case LOG_ALERT:
        case LOG_CRIT:
        case LOG_ERR:
            strcat(msgbuf, "ERROR: ");
	    break;
        default:
            return;
    }

    va_start (args, fmt);
    strncat(msgbuf, fmt, MAXLEN-10);
    if (!quiet) {  /* stderr */
        vfprintf(stderr, msgbuf, args);
    }
    if (log) {  /* syslog */
        memset(logbuf, 0, sizeof(logbuf));
        vsnprintf(logbuf, MAXLEN-1, msgbuf, args);
	syslog(level, "%s", logbuf);
    }
}


void *xmalloc (size_t size)
{
    void *pt;

    pt = (void *)malloc(size);
    if (!pt) {
       perror("malloc: not enough memory.");
       exit(1);
    }
    memset(pt, 0, size);
    return pt;
}
