/****************************************************************************** * Copyright (c) 2000-2020 Ericsson Telecom AB * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html * * Contributors: * Baji, Laszlo * Balasko, Jeno * Gecse, Roland * Raduly, Csaba * Szabados, Kristof * Szabo, Janos Zoltan - initial implementation * ******************************************************************************/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> #include <sys/wait.h> #include <mysql.h> #include <openssl/rand.h> #include <openssl/sha.h> #include <openssl/dsa.h> #include <openssl/pem.h> #include <openssl/err.h> #include "../common/memory.h" #include "../common/license.h" #define MYSQL_HOST "ttcn3-mysql-775.seli.gic.ericsson.se" #define MYSQL_USER "titan_dbu" #define MYSQL_PASSWORD "xxx" #define MYSQL_DATABASE "ttcn3" #define MYSQL_PORT 6446 #define LICENSE_DIR "/proj/ttcn3titan/license" #define PRIVATE_KEY LICENSE_DIR "/key.pem" #define SECS_IN_DAY 86400 __attribute__ ((__format__ (__printf__, 1, 2), __noreturn__)) static void error(const char *fmt, ...) { va_list pvar; fputs("ERROR: ", stderr); va_start(pvar, fmt); vfprintf(stderr, fmt, pvar); va_end(pvar); fputc('\n', stderr); exit(EXIT_FAILURE); } /* Big-endian */ static void encode_int(unsigned char *to, unsigned int from) { to[3] = from & 0xFF; from >>= 8; to[2] = from & 0xFF; from >>= 8; to[1] = from & 0xFF; from >>= 8; to[0] = from & 0xFF; } static void encode_string(char *to, const char *from, size_t length) { strncpy(to, from, length); } static void encode_license(license_raw *to, const license_struct *from) { memset(to, 0, sizeof(*to)); encode_int(to->unique_id, from->unique_id); encode_string(to->licensee_name, from->licensee_name, sizeof(to->licensee_name)); encode_string(to->licensee_email, from->licensee_email, sizeof(to->licensee_email)); encode_string(to->licensee_company, from->licensee_company, sizeof(to->licensee_company)); encode_string(to->licensee_department, from->licensee_department, sizeof(to->licensee_department)); encode_int(to->valid_from, from->valid_from); encode_int(to->valid_until, from->valid_until); encode_int(to->host_id, from->host_id); encode_string(to->login_name, from->login_name, sizeof(to->login_name)); encode_int(to->from_major, from->from_major); encode_int(to->from_minor, from->from_minor); encode_int(to->from_patchlevel, from->from_patchlevel); encode_int(to->to_major, from->to_major); encode_int(to->to_minor, from->to_minor); encode_int(to->to_patchlevel, from->to_patchlevel); encode_int(to->feature_list, from->feature_list); encode_int(to->limitation_type, from->limitation_type); encode_int(to->max_ptcs, from->max_ptcs); } static void sign_license(license_raw *lptr, const char *dsa_key_file) { unsigned char message_digest[20]; SHA_CTX sha_ctx; unsigned int signature_len = sizeof(lptr->dsa_signature); DSA *dsa; FILE *fp = fopen(dsa_key_file, "r"); if (fp == NULL) { error("Cannot open DSA private key file `%s' for reading: %s", dsa_key_file, strerror(errno)); } dsa = PEM_read_DSAPrivateKey(fp, NULL, NULL, NULL); fclose(fp); if (dsa == NULL) { error("Cannot read DSA private key from `%s': %s", dsa_key_file, ERR_error_string(ERR_get_error(), NULL)); } SHA1_Init(&sha_ctx); SHA1_Update(&sha_ctx, lptr, sizeof(*lptr) - sizeof(lptr->dsa_signature)); SHA1_Final(message_digest, &sha_ctx); if ((int)signature_len != DSA_size(dsa)) { error("Invalid DSA signature size: %d", DSA_size(dsa)); } if (!DSA_sign(0, message_digest, sizeof(message_digest), lptr->dsa_signature, &signature_len, dsa)) { error("DSA signature generation failed: %s", ERR_error_string(ERR_get_error(), NULL)); } DSA_free(dsa); } static void write_license(const char *file_name, const license_raw *lptr) { FILE *fp = fopen(file_name, "w"); if (fp == NULL) { error("Cannot open license file `%s' for writing: %s", file_name, strerror(errno)); } if (!PEM_write(fp, "TTCN-3 LICENSE FILE", "", (unsigned char *)lptr, sizeof(*lptr))) { error("Writing to license file `%s' failed: %s", file_name, ERR_error_string(ERR_get_error(), NULL)); } fclose(fp); } static int privileged_user = 0; static uid_t my_uid = -1; static void check_user(void) { // if (geteuid() != 45719) { // error("This program must have set-uid to user etccadmi1 (uid 45719)"); // } my_uid = getuid(); switch (my_uid) { case 45719: /* etccadmi1 */ privileged_user = 1; case 34217: /* ethjra */ case 34385: /* ethgasz */ case 98994: /* titanrt */ break; default: error("You are not allowed to use this program"); } } static void connect_database(MYSQL *mysql) { if (!mysql_init(mysql)) { error("MySQL structure initialization failed"); } if (!mysql_real_connect(mysql, MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, NULL, 0)) { error("MySQL database connection failed: %s", mysql_error(mysql)); } else { fputs("Connected to MySQL database.\n", stderr); } } static void disconnect_database(MYSQL *mysql) { mysql_close(mysql); fputs("Disconnected from MySQL database.\n", stderr); } static void fill_from_database_c(MYSQL *mysql, license_struct *plic, int unique_id) { MYSQL_RES *result; my_ulonglong num_rows; unsigned int i, num_fields; MYSQL_FIELD *fields; MYSQL_ROW row; time_t current_time = time(NULL); char *query = mprintf("SELECT * FROM licenses WHERE unique_id = %d", unique_id); if (mysql_query(mysql, query)) { error("Execution of MySQL query `%s' failed: %s", query, mysql_error(mysql)); } Free(query); result = mysql_store_result(mysql); if (result == NULL) { error("MySQL query result fetching failed: %s", mysql_error(mysql)); } num_rows = mysql_num_rows(result); if (num_rows <= 0) { error("There is no license in the database with unique id %d", unique_id); } else if (num_rows > 1) { error("There are %llu licenses in the database with unique id %d", num_rows, unique_id); } num_fields = mysql_num_fields(result); fields = mysql_fetch_fields(result); row = mysql_fetch_row(result); fprintf(stderr, "License data was fetched for unique id %d.\n", unique_id); for (i = 0; i < num_fields; i++) { const char *field_name = fields[i].name; if (!strcmp(field_name, "unique_id")) { plic->unique_id = atoi(row[i]); } else if (!strcmp(field_name, "licensee_name")) { plic->licensee_name = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "licensee_email")) { plic->licensee_email = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "contact_name")) { plic->contact_name = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "contact_email")) { plic->contact_email = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "send_to")) { if (!strcmp(row[i], "Contact")) plic->send_to = SENDTO_CONTACT; else if (!strcmp(row[i], "Both")) plic->send_to = SENDTO_BOTH; else /*if (!strcmp(row[i], "Licensee"))*/ plic->send_to = SENDTO_LICENSEE; /* SENDTO_LICENSEE is the default */ } else if (!strcmp(field_name, "licensee_company")) { plic->licensee_company = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "licensee_department")) { plic->licensee_department = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "valid_from")) { int year, month, day; struct tm tm_struct; if (sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day) != 3) { error("Invalid date format: `%s'", row[i]); } tm_struct.tm_year = year - 1900; tm_struct.tm_mon = month - 1; tm_struct.tm_mday = day; tm_struct.tm_hour = 0; tm_struct.tm_min = 0; tm_struct.tm_sec = 0; tm_struct.tm_isdst = -1; plic->valid_from = mktime(&tm_struct); } else if (!strcmp(field_name, "valid_until")) { int year, month, day; struct tm tm_struct; if (sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day) != 3) { error("Invalid date format: `%s'", row[i]); } tm_struct.tm_year = year - 1900; tm_struct.tm_mon = month - 1; tm_struct.tm_mday = day; tm_struct.tm_hour = 23; tm_struct.tm_min = 59; tm_struct.tm_sec = 59; tm_struct.tm_isdst = -1; plic->valid_until = mktime(&tm_struct); } else if (!strcmp(field_name, "host_id")) { if (row[i] == NULL || sscanf(row[i], "%lx", &plic->host_id) != 1) plic->host_id = 0; } else if (!strcmp(field_name, "login_name")) { plic->login_name = row[i] != NULL ? row[i] : ""; } else if (!strcmp(field_name, "from_major")) { plic->from_major = atoi(row[i]); } else if (!strcmp(field_name, "from_minor")) { plic->from_minor = atoi(row[i]); } else if (!strcmp(field_name, "from_patchlevel")) { plic->from_patchlevel = atoi(row[i]); } else if (!strcmp(field_name, "to_major")) { plic->to_major = atoi(row[i]); } else if (!strcmp(field_name, "to_minor")) { plic->to_minor = atoi(row[i]); } else if (!strcmp(field_name, "to_patchlevel")) { plic->to_patchlevel = atoi(row[i]); } else if (!strcmp(field_name, "feature_ttcn3")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_TTCN3; } else if (!strcmp(field_name, "feature_asn1")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_ASN1; } else if (!strcmp(field_name, "feature_codegen")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_CODEGEN; } else if (!strcmp(field_name, "feature_raw")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_RAW; } else if (!strcmp(field_name, "feature_text")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_TEXT; } else if (!strcmp(field_name, "feature_ber")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_BER; } else if (!strcmp(field_name, "feature_per")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_PER; } else if (!strcmp(field_name, "feature_xer")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_XER; } else if (!strcmp(field_name, "feature_tpgen")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_TPGEN; } else if (!strcmp(field_name, "feature_single")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_SINGLE; } else if (!strcmp(field_name, "feature_mctr")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_MCTR; } else if (!strcmp(field_name, "feature_hc")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_HC; } else if (!strcmp(field_name, "feature_logformat")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_LOGFORMAT; } else if (!strcmp(field_name, "feature_gui")) { if (!strcmp(row[i], "Yes")) plic->feature_list |= FEATURE_GUI; } else if (!strcmp(field_name, "limit_host")) { if (!strcmp(row[i], "Yes")) plic->limitation_type |= LIMIT_HOST; } else if (!strcmp(field_name, "limit_user")) { if (!strcmp(row[i], "Yes")) plic->limitation_type |= LIMIT_USER; } else if (!strcmp(field_name, "max_ptcs")) { plic->max_ptcs = atoi(row[i]); } } /* next */ if (plic->valid_from > current_time) { error("The beginning of validity is in the future: %s", ctime(&plic->valid_from)); } if (plic->valid_until < current_time) { error("The license has already expired on %s", ctime(&plic->valid_until)); } else if (plic->valid_until < current_time + SECS_IN_DAY) { error("The license is valid for less than one day"); } else if (plic->valid_until > current_time + 380 * SECS_IN_DAY && !privileged_user) { error("You are not authorized to generate a license that is valid for " "more than one year (%ld days)", (plic->valid_until - current_time) / SECS_IN_DAY); } if (plic->limitation_type & LIMIT_HOST && plic->host_id == 0) { error("Cannot generate HOST limited license for zero host ID"); } if (plic->limitation_type & LIMIT_USER && !strcmp(plic->login_name, "")) { error("Cannot generate USER limited license for empty login name"); } if (plic->limitation_type == 0 && !privileged_user) { error("You are not authorized to generate unrestricted licenses"); } mysql_free_result(result); } static void fill_from_database(MYSQL *mysql, license_raw *lraw, int unique_id) { license_struct lstr; memset(&lstr, 0, sizeof(lstr)); fill_from_database_c(mysql, &lstr, unique_id); encode_license(lraw, &lstr); } static const char *get_email_address(void) { switch (my_uid) { case 34217: /* ethjra */ return "Julianna.Rozsa@ericsson.com"; case 34385: /* ethgasz */ return "Gabor.Szalai@ericsson.com"; case 45719: /* etccadmi1 */ return "Julianna.Rozsa@ericsson.com"; default: return "???"; } } static const char *get_first_name(void) { switch (my_uid) { case 34217: /* ethjra */ return "Julianna"; case 34385: /* ethgasz */ return "Gábor"; case 45719: /* etccadmi1 */ return "Admin"; default: return "???"; } } static const char *get_email_signature(void) { switch (my_uid) { case 34217: /* ethjra */ return "Julianna Rózsa Ericsson Hungary Ltd.\n" "Configuration Manager \n" "Test Competence Center Phone: +36 1 437 7895\n" "Julianna.Rozsa@ericsson.com Fax: +36 1 439 5176\n"; case 34385: /* ethgasz */ return "Gábor Szalai Gabor.Szalai@ericsson.com\n" "Test Competence Center Tel: +36-1-437-7591\n" "Ericsson Hungary Ltd. Fax: +36-1-439-5176\n" "H-1117 Budapest, Magyar tudósok körútja 11., Hungary Mob: +36-30-743-7669\n" "Intranet: http://ttcn.ericsson.se/\n"; case 45719: /* etccadmi1 */ return "TCC License Administrator Julianna.Rozsa@ericsson.com\n" "Test Competence Center \n" "Ericsson Hungary Ltd. Fax: +36-1-439-5176\n" "H-1117 Budapest, \n" "Magyar tudósok körútja, Hungary\n" "Intranet: http://ttcn.ericsson.se/\n"; default: return ""; } } static void mail_license(const license_struct *lstr, const license_raw *lptr) { /* administrator's email address */ const char *email_addr; /* licensee's email address, or empty if sent only to contact */ const char *maybe_licensee_email = (SENDTO_CONTACT != lstr->send_to) ? lstr->licensee_email : ""; /* contact's email address, or empty if sent only to licensee */ const char *maybe_contact_email = (SENDTO_LICENSEE != lstr->send_to) ? lstr->contact_email : ""; char *sendmail_cmd, *first_name, *mime_boundary; FILE *fp; size_t i; time_t current_time; struct tm *formatted_time; int return_status; email_addr = get_email_address(); sendmail_cmd = mprintf("/usr/lib/sendmail %s %s %s", maybe_licensee_email, maybe_contact_email, email_addr); fp = popen(sendmail_cmd, "w"); Free(sendmail_cmd); if (fp == NULL) error("Cannot mail license: %s", strerror(errno)); first_name = memptystr(); for (i = 0; lstr->licensee_name[i] != '\0'; i++) { if (lstr->licensee_name[i] == ' ') { if (first_name[0] != '\0') break; } else first_name = mputc(first_name, lstr->licensee_name[i]); } mime_boundary = mcopystr("__"); for (i = 0; i < 16; i++) { unsigned char uc; if (!RAND_pseudo_bytes(&uc, 1)) error("Random number generation " "failed: %s", ERR_error_string(ERR_get_error(), NULL)); mime_boundary = mputprintf(mime_boundary, "%02X", uc); } mime_boundary = mputstr(mime_boundary, "__"); fprintf(fp, "From: TTCN-3 License Generator <%s>\n" "To: %s <%s>\n" "Subject: TTCN-3 license key\n" "MIME-Version: 1.0\n" "Content-Type: multipart/mixed; boundary=\"%s\"\n" "\n" "This is a multi-part message in MIME format.\n" "--%s\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "\n" "Dear %s,\n" "\n", email_addr, lstr->licensee_name, lstr->licensee_email, mime_boundary, mime_boundary, first_name); Free(first_name); current_time = time(NULL); formatted_time = localtime(&lstr->valid_until); if (formatted_time == NULL) { error("Cannot format time: %s", strerror(errno)); } if (current_time - lstr->valid_from < SECS_IN_DAY) { fprintf(fp, "this is your license key for the TTCN-3 " "Test Executor. Please save the entire attachment " "(including the header and trailing lines) and " "follow the instructions of the Installation Guide " "(Section 4.1.1) to proceed with the installation.\n" "\n" "The Unique ID of your license key is %d. Please refer to " "this number if you have any licensing-related problems.\n" "\n" "The license key is valid till %04d-%02d-%02d beginning from today.\n" "\n" "Do not hesitate to contact us if you have any questions about " "the TTCN-3 language or the test executor.\n", lstr->unique_id, formatted_time->tm_year + 1900, formatted_time->tm_mon + 1, formatted_time->tm_mday); } else { fprintf(fp, "your existing license key with Unique ID %d has been " "prolonged so that it will expire on %04d-%02d-%02d. " "Please find the updated license file in the attachment. " "The only thing you have to do is to replace your old " "license file with this one.\n", lstr->unique_id, formatted_time->tm_year + 1900, formatted_time->tm_mon + 1, formatted_time->tm_mday); } fprintf(fp, "\n" "With best regards,\n" "%s\n" "\n" "PS: This is an automatically generated e-mail, please reply to its " "sender in case of problems!\n" "\n", get_first_name()); if (SENDTO_CONTACT == lstr->send_to) { fprintf (stderr, "%s, please forward this email to %s (%s)\n\n", lstr->contact_name, lstr->licensee_name, lstr->licensee_email); } fprintf(fp, "--\n" "%s" "--%s\n" "Content-Type: application/octet-stream\n" "Content-Transfer-Encoding: 7bit\n" "Content-Disposition: attachment; filename=\"license_%d.dat\"\n" "\n", get_email_signature(), mime_boundary, lstr->unique_id); if (!PEM_write(fp, "TTCN-3 LICENSE FILE", "", (unsigned char*)lptr, sizeof(*lptr))) { error("Attaching license file failed: %s", ERR_error_string(ERR_get_error(), NULL)); } fprintf(fp, "\n" "--%s--\n", mime_boundary); Free(mime_boundary); return_status = pclose(fp); if (!WIFEXITED(return_status) || WEXITSTATUS(return_status) != EXIT_SUCCESS) { error("Sendmail invocation failed"); } } static int get_unique_id(const char *str) { int unique_id = atoi(str); if (unique_id <= 0) { error("Invalid unique id: %s", str); } return unique_id; } int main(int argc, char *argv[]) { license_struct lstr; license_raw lraw; char *file_name; int unique_id; MYSQL mysql; int send_email; fputs("License generator for the TTCN-3 Test Executor.\n", stderr); check_user(); switch (argc) { case 2: unique_id = get_unique_id(argv[1]); send_email = 0; break; case 3: if (!strcmp(argv[1], "-m")) { unique_id = get_unique_id(argv[2]); send_email = 1; break; } /* no break */ default: error("Usage: %s [-m] unique_id", argv[0]); } init_openssl(); connect_database(&mysql); fill_from_database(&mysql, &lraw, unique_id); disconnect_database(&mysql); sign_license(&lraw, PRIVATE_KEY); file_name = mprintf(LICENSE_DIR "/license_%d.dat", unique_id); write_license(file_name, &lraw); fprintf(stderr, "License file %s was generated.\n", file_name); fputs("License information:\n", stderr); load_license_from_file(&lstr, file_name); Free(file_name); print_license(&lstr); if (send_email) { license_struct lstr2; connect_database(&mysql); fill_from_database_c(&mysql, &lstr2, unique_id); disconnect_database(&mysql); mail_license(&lstr2, &lraw); fputs("E-mail was sent.\n", stderr); /* do not free_license(lstr2) */ } free_openssl(); free_license(&lstr); check_mem_leak(argv[0]); return EXIT_SUCCESS; }