Author: marten Date: Mon Oct 19 22:47:11 2009 New Revision: 1240 Log: Community-Operator client stores received certificates to a directory and checks periodically if a new certificate is required Modified: trunk/community-operator/Makefile.am trunk/community-operator/co_client.c Modified: trunk/community-operator/Makefile.am ============================================================================== --- trunk/community-operator/Makefile.am Mon Oct 19 21:51:52 2009 (r1239) +++ trunk/community-operator/Makefile.am Mon Oct 19 22:47:11 2009 (r1240) @@ -20,6 +20,7 @@ INCLUDES += -I@PISA_HIPL_SRCDIR@/libhiptool INCLUDES += -I@PISA_HIPL_SRCDIR@/libdht INCLUDES += -I@PISA_HIPL_SRCDIR@/opendht +INCLUDES += -I@PISA_HIPL_SRCDIR@/firewall INCLUDES += -I@PISA_HIPL_SRCDIR@/hipd INCLUDES += -I@PISA_HIPL_SRCDIR@/i3/i3_client INCLUDES += -I@PISA_HIPL_SRCDIR@/pjproject/pjnath/include @@ -33,6 +34,13 @@ endif co_server_SOURCES = co_server.c hipl.c +if PISA_WITH_HIPL +# TODO correct linking +# pisa_split_cert function is needed in co_client.c +# Do not know any better way so far. + co_client_LDADD = @PISA_HIPL_SRCDIR@/firewall/pisa_cert.o +endif + co_client_SOURCES = co_client.c include_HEADERS = hipl.h Modified: trunk/community-operator/co_client.c ============================================================================== --- trunk/community-operator/co_client.c Mon Oct 19 21:51:52 2009 (r1239) +++ trunk/community-operator/co_client.c Mon Oct 19 22:47:11 2009 (r1240) @@ -3,38 +3,60 @@ * All rights reserved. */ -#include <errno.h> -#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <signal.h> +#include <unistd.h> +#include <dirent.h> + +#include "global.h" + +#define __USE_XOPEN +#include <time.h> #include "co_common_packets.h" #include "config.h" #include "debug.h" -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> +#ifdef CONFIG_PISA_WITH_HIPL +# include "pisa_cert.h" /* in firewall under the hipl source tree */ +#endif /* CONFIG_PISA_WITH_HIPL */ + +#define CERT_FILENAME_FORMAT "%Y%m%d%H%M%S" #define PATH_BUFFER 1000 +static int check_certs_dir(void); +static void co_client_check_certs(int signal); static void co_client_init(void); +static void co_client_main(void); +static void co_client_quit(int signal); +static int copy_cert(const char * source, char * destination); static int create_client_socket(struct sockaddr_in6 *srv_addr); +static int is_valid_cert(const char *filename); static void print_deny_reason(int reason); static int receive_certificate(char **cert); +static int retrieve_new_cert(void); static int request_cert(void); -static int write_cert_file(char *cert, const char *filename); +static int write_cert_file(const char *cert, const char *filename); +#ifdef CONFIG_PISA_WITH_HIPL +static void save_cert_to_certs_dir(const char * cert); +#endif /* CONFIG_PISA_WITH_HIPL*/ typedef struct co_client_conf { int retries; char srv_hit[INET6_ADDRSTRLEN]; int port; char cert_filename[PATH_BUFFER]; + int check_interval; + char certs_dir[PATH_BUFFER]; + int save_certs; } co_client_conf; co_client_conf co_client_config; static int sock; +char last_used_cert[PATH_BUFFER]; /** * Write a certificate to a file. @@ -43,7 +65,7 @@ * @param filename the filename (file will be overwritten) * @return 0 on success, -1 otherwise */ -static int write_cert_file(char *cert,const char *filename) +static int write_cert_file(const char *cert,const char *filename) { FILE *f; @@ -191,6 +213,73 @@ } /** + * Terminate community-operator client by receiving signal + * @param signal signal quit code + */ +static void co_client_quit(int signal) +{ + PISA_INFO("Shutting down Community-Operator client...\n"); + pisa_cfg_cleanup(); + exit(EXIT_SUCCESS); +} + +/** + * Main loop + */ +static void co_client_main(void) +{ + select(0,NULL,NULL,NULL,NULL); +} + +/** + * Copies cert file to destination. + * @param source Source filename + * @param destination Destination filename + * @return 1 -> success + * @return 0 -> error + */ +static int copy_cert(const char * source, char * destination) +{ + FILE *from, *to; + char ch; + + if ((from = fopen(source, "r")) == NULL) { + perror("open"); + return 0; + } + + if ((to = fopen(destination, "w")) == NULL) { + perror("open"); + return 0; + } + + while (!feof(from)) { + ch = fgetc(from); + if (ferror(from)) { + PISA_ERROR("Error reading source file.\n"); + return 0; + } + if (!feof(from)) + fputc(ch, to); + if (ferror(to)) { + PISA_ERROR("Error writing destination file.\n"); + return 0; + } + } + + if (fclose(from) == EOF) { + PISA_ERROR("Error closing source file.\n"); + return 0; + } + + if (fclose(to) == EOF) { + PISA_ERROR("Error closing destination file.\n"); + return 0; + } + return 1; +} + +/** * Reads the settings from the config file. */ static void co_client_init(void) @@ -202,6 +291,10 @@ pisa_cfg_get_int_value("retry", &co_client_config.retries); pisa_cfg_get_string_value("filename", co_client_config.cert_filename, PATH_BUFFER); + pisa_cfg_get_int_value("check_interval", &co_client_config.check_interval); + pisa_cfg_get_string_value("certs_dir", co_client_config.certs_dir, + PATH_BUFFER); + pisa_cfg_get_bool_value("save_certs", &co_client_config.save_certs); /* sanity check configuration values */ if (co_client_config.retries < 1) @@ -209,12 +302,17 @@ if (co_client_config.retries > 100) /* @todo find reasonable maximum */ co_client_config.retries = 100; + if (co_client_config.check_interval <= 0) + co_client_config.check_interval = 60; } -int main(int argc, char **argv) +/** + * Retrieves new certificates from community-operator server + * @return 1 -> success + * @return 0 -> error + */ +static int retrieve_new_cert(void) { - co_client_init(); - struct sockaddr_in6 srv_addr = { 0 }; char *cert = NULL; int count; @@ -231,14 +329,203 @@ break; } + close(sock); + + // Successfully received certificate if (cert) { - write_cert_file(cert, "test-cert"); + PISA_INFO("Certificate was successfully retrieved.\n"); +#ifdef CONFIG_PISA_WITH_HIPL + save_cert_to_certs_dir(cert); +#else + write_cert_file(cert, co_client_config.cert_filename); +#endif /* CONFIG_PISA_WITH_HIPL */ free(cert); - printf("Certificate was successfully retrieved.\n"); - } else - printf("Certificate could not be obtained.\n"); + return 1; + } else { + PISA_ERROR("Certificate could not be obtained.\n"); + return 0; + } - pisa_cfg_cleanup(); - close(sock); + return 1; +} + +#ifdef CONFIG_PISA_WITH_HIPL +static void save_cert_to_certs_dir(const char * cert) +{ + // Save certificate to co_client_config.certs_dir + if(co_client_config.save_certs) { + struct pisa_cert pc; + struct tm *ts; + char buf_before[15]; + char buf_after[15]; + char path[100]; + time_t now; + char *cert_start; + + cert_start = strstr(cert,"(cert"); + // Create pisa_struct from cert file + pisa_split_cert(cert_start, &pc); + + // Convert dates to format yyyymmddhhmmss + PISA_DEBUG(PL_GENERIC,"Certificate is valid from: %s",ctime(&pc.not_before)); + ts = localtime(&pc.not_before); + strftime(buf_before, sizeof(buf_before), CERT_FILENAME_FORMAT, ts); + ts = localtime(&pc.not_after); + PISA_DEBUG(PL_GENERIC,"Certificate expires at: %s",ctime(&pc.not_after)); + strftime(buf_after, sizeof(buf_after), CERT_FILENAME_FORMAT, ts); + + // Create filename notbefore-notaftercert (yyyymmddhhmmss-yyyymmddhhmmsscert) + // in co_client_config.certs_dir + snprintf((char *)path,sizeof(path),"%s/%s-%scert",co_client_config.certs_dir ,buf_before,buf_after); + write_cert_file(cert, (const char *)path); + time(&now); + + // Certificate is already valid + // Copy to final location + if(difftime(now,pc.not_before)>=0) + { + PISA_DEBUG(PL_GENERIC,"Copying to cert location...\n"); + strncpy(last_used_cert,path,PATH_BUFFER); + write_cert_file(cert,(const char *)co_client_config.cert_filename); + } + else + { + PISA_DEBUG(PL_GENERIC,"Certificate not yet valid.\n"); + } + } +} +#endif /* CONFIG_PISA_WITH_HIPL */ + +/** + * Checks expiration dates of a cert filename + * @param filename filename to check + * @return 1 -> cert is valid + * @return 0 -> cert not yet valid + * @return -1 -> cert expired + */ +static int is_valid_cert(const char *filename) +{ + struct tm ts_before, ts_after; + char buf_before[15]; + char buf_after[15]; + time_t now, not_before, not_after; + + // Read not-before date + strncpy(buf_before, filename, 14); + strptime(buf_before, CERT_FILENAME_FORMAT, &ts_before); + + // Read not-after date + strncpy(buf_after, filename + 15, 14); + strptime(buf_after, CERT_FILENAME_FORMAT, &ts_after); + + time(&now); + not_before = mktime(&ts_before); + not_after = mktime(&ts_after); + + // Expired + if (difftime(now, not_after) > 0) { + PISA_DEBUG(PL_GENERIC, "Cert: %s expired.\n", filename); + return -1; + } + + // Not yet valid + if (difftime(now, not_before) < 0) { + PISA_DEBUG(PL_GENERIC, "Cert: %s is not yet valid.\n", filename); + return 0; + } + + PISA_DEBUG(PL_GENERIC, "Cert: %s is valid.\n", filename); + return 1; +} + +/** + * Checks if there is a valid cert in co_client_config.certs_dir + * and copies the cert to co_client_config.filename + * @return 1 -> a valid cert file exists + * @return 0 -> no valid cert file found + */ +static int check_certs_dir(void) +{ + struct dirent **namelist; + int n, i, found, result; + char path[PATH_BUFFER]; + + found = 0; + n = scandir(co_client_config.certs_dir, &namelist, 0, alphasort); + if (n < 0) { + perror("scandir"); + } else { + for (i = 0; i < n && !found; i++) { + // Skip . and .. + if (strcmp(namelist[i]->d_name, ".") != 0 && + strcmp(namelist[i]->d_name, "..") != 0) { + result = is_valid_cert(namelist[i]->d_name); + snprintf((char *) path, sizeof(path), "%s/%s", + co_client_config.certs_dir, namelist[i]->d_name); + + // Certificate expired + // Remove certificate + if (result == -1) { + PISA_DEBUG(PL_GENERIC, "Removing cert...\n"); + if (remove(path) != 0) { + perror("Removing cert failed"); + } + } else if (result) { + found = 1; + // It is a new certificate + // Copy to final destination + if (strcmp(path, last_used_cert) != 0) { + strncpy(last_used_cert, path, PATH_BUFFER); + PISA_DEBUG(PL_GENERIC, "Found valid cert.\n"); + PISA_DEBUG(PL_GENERIC, "Copying to cert location...\n"); + copy_cert(path, co_client_config.cert_filename); + } + } + } + free(namelist[i]); + } + free(namelist); + } + return found; +} + +/** + * Function is called periodically to retrieve new certs if old certs in + * co_client_config.certs_dir expired. + * @param signal signal code + */ +static void co_client_check_certs(int signal) +{ + if (co_client_config.save_certs) { + PISA_INFO("Checking existing certificates...\n"); + if (!check_certs_dir()) { + PISA_INFO("Did not find a valid cert\n"); + PISA_INFO("Requesting new cert...\n"); + retrieve_new_cert(); + } + alarm(co_client_config.check_interval); + } else { + retrieve_new_cert(); + } +} + +int main(int argc, char **argv) +{ + + signal(SIGTERM, co_client_quit); + signal(SIGINT, co_client_quit); + signal(SIGQUIT, co_client_quit); + signal(SIGALRM, co_client_check_certs); + + co_client_init(); + + co_client_check_certs(SIGALRM); + + // Only enter infinite loop if we periodically check for valid certs + if (co_client_config.save_certs) { + co_client_main(); + } + + co_client_quit(0); return 0; }