/* utils.c * */ /* fics - An internet chess server. Copyright (C) 1993 Richard V. Nash 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. */ /* Revision history: name email yy/mm/dd Change Richard Nash 93/10/22 Created Markus Uhlin 23/12/10 Fixed compiler warnings (plus more) Markus Uhlin 23/12/10 Deleted check_emailaddr() Markus Uhlin 23/12/17 Reformatted functions Markus Uhlin 23/12/25 Revised Markus Uhlin 24/03/16 Replaced unbounded string handling functions. Markus Uhlin 24/07/05 Fixed unusual struct allocations and replaced strcpy() with strlcpy(). Markus Uhlin 24/07/07 Made certain functions private Markus Uhlin 24/07/07 Added parameter 'size' to psprintf_highlight(). Markus Uhlin 24/08/11 Improved fix_time(). Markus Uhlin 24/12/01 Usage of sizeof and fixed ignored fgets() retvals. */ #include "stdinclude.h" #include "common.h" #include #include "config.h" #include "network.h" #include "playerdb.h" #include "rmalloc.h" #include "utils.h" #if __linux__ #include #endif struct t_tree { struct t_tree *left, *right; char *name; }; struct t_dirs { struct t_dirs *left, *right; time_t mtime; struct t_tree *files; char *name; }; PRIVATE char** t_buffer = NULL; PRIVATE int t_buffersize = 0; PUBLIC int count_lines(FILE *fp) { int c, nl = 0; while ((c = fgetc(fp)) != EOF) if (c == '\n') ++nl; return nl; } PUBLIC int iswhitespace(int c) { if (c < ' ' || c == '\b' || c == '\n' || c == '\t' || c == ' ') return 1; return 0; } PUBLIC char * getword(char *str) { int i; static char word[MAX_WORD_SIZE]; i = 0; while (*str && !iswhitespace(*str)) { word[i] = *str; str++; i++; if (i == MAX_WORD_SIZE) { i = (i - 1); break; } } word[i] = '\0'; return word; } /* * Returns a pointer to the first whitespace in the argument. */ PUBLIC char * eatword(char *str) { while (*str && !iswhitespace(*str)) str++; return str; } /* * Returns a pointer to the first non-whitespace char in the argument. */ PUBLIC char * eatwhite(char *str) { while (*str && iswhitespace(*str)) str++; return str; } /* * Returns a pointer to the same string with trailing spaces removed. */ PUBLIC char * eattailwhite(char *str) { int len; if (str == NULL) return NULL; len = strlen(str); while (len > 0 && iswhitespace(str[len - 1])) len--; str[len] = '\0'; return str; } /* * Returns the next word in a given string. */ PUBLIC char * nextword(char *str) { return eatwhite(eatword(str)); } PUBLIC int mail_string_to_address(char *addr, char *subj, char *str) { FILE *fp; char com[1000]; #ifdef SENDMAILPROG snprintf(com, sizeof com, "%s\n", SENDMAILPROG); #else snprintf(com, sizeof com, "%s -s \"%s\" %s", MAILPROGRAM, subj, addr); #endif fp = popen(com, "w"); if (!fp) return -1; #ifdef SENDMAILPROG fprintf(fp, "To: %s\nSubject: %s\n%s", addr, subj, str); #else fprintf(fp, "%s", str); #endif pclose(fp); return 0; } PUBLIC int mail_string_to_user(int p, char *subj, char *str) { if (parray[p].emailAddress && parray[p].emailAddress[0] && safestring(parray[p].emailAddress)) { return mail_string_to_address(parray[p].emailAddress, subj, str); } else { return -1; } } PUBLIC int mail_file_to_address(char *addr, char *subj, char *fname) { FILE *fp1, *fp2; char com[1000] = { '\0' }; char tmp[MAX_LINE_SIZE] = { '\0' }; /* maybe unused */ (void) fp2; (void) tmp; #ifdef SENDMAILPROG snprintf(com, sizeof com, "%s\n", SENDMAILPROG); #else snprintf(com, sizeof com, "%s -s \"%s\" %s < %s&", MAILPROGRAM, subj, addr, fname); #endif if ((fp1 = popen(com, "w")) == NULL) return -1; #ifdef SENDMAILPROG if ((fp2 = fopen(fname, "r")) == NULL) { pclose(fp1); return -1; } fprintf(fp1, "To: %s\nSubject: %s\n", addr, subj); while (fgets(tmp, sizeof tmp, fp2) != NULL && !feof(fp2)) fputs(tmp, fp1); fclose(fp2); #endif pclose(fp1); return 0; } PUBLIC int mail_file_to_user(int p, char *subj, char *fname) { if (parray[p].emailAddress && parray[p].emailAddress[0] && safestring(parray[p].emailAddress)) { return mail_file_to_address(parray[p].emailAddress, subj, fname); } else { return -1; } } /* * Process a command for a user */ PUBLIC int pcommand(int p, char *comstr, ...) { char tmp[MAX_LINE_SIZE] = { '\0' }; int current_socket = parray[p].socket; int retval; va_list ap; va_start(ap, comstr); vsnprintf(tmp, sizeof tmp, comstr, ap); va_end(ap); retval = process_input(current_socket, tmp); if (retval == COM_LOGOUT) { process_disconnection(current_socket); net_close_connection(current_socket); } return retval; } PUBLIC int pprintf(int p, const char *format, ...) { char tmp[10 * MAX_LINE_SIZE]; int retval; va_list ap; va_start(ap, format); retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); net_send_string(parray[p].socket, tmp, 1); return retval; } PRIVATE void pprintf_dohightlight(int p) { if (parray[p].highlight & 0x01) pprintf(p, "\033[7m"); if (parray[p].highlight & 0x02) pprintf(p, "\033[1m"); if (parray[p].highlight & 0x04) pprintf(p, "\033[4m"); if (parray[p].highlight & 0x08) pprintf(p, "\033[2m"); } PUBLIC int pprintf_highlight(int p, char *format, ...) { char tmp[10 * MAX_LINE_SIZE]; int retval; va_list ap; pprintf_dohightlight(p); va_start(ap, format); retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); net_send_string(parray[p].socket, tmp, 1); if (parray[p].highlight) pprintf(p, "\033[0m"); return retval; } PRIVATE void sprintf_dohightlight(int p, char *s, size_t size) { if (parray[p].highlight & 0x01) strlcat(s, "\033[7m", size); if (parray[p].highlight & 0x02) strlcat(s, "\033[1m", size); if (parray[p].highlight & 0x04) strlcat(s, "\033[4m", size); if (parray[p].highlight & 0x08) strlcat(s, "\033[2m", size); } PUBLIC int psprintf_highlight(int p, char *s, size_t size, char *format, ...) { int retval; va_list ap; if (parray[p].highlight) { char tmp[1000] = { '\0' }; va_start(ap, format); retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); sprintf_dohightlight(p, s, size); strlcat(s, tmp, size); strlcat(s, "\033[0m", size); } else { va_start(ap, format); retval = vsnprintf(s, size, format, ap); va_end(ap); } return retval; } PUBLIC int pprintf_prompt(int p, char *format, ...) { char tmp[10 * MAX_LINE_SIZE]; int retval; va_list ap; va_start(ap, format); retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); net_send_string(parray[p].socket, tmp, 1); net_send_string(parray[p].socket, parray[p].prompt, 1); return retval; } PUBLIC int pprintf_noformat(int p, char *format, ...) { char tmp[10 * MAX_LINE_SIZE]; int retval; va_list ap; va_start(ap, format); retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); net_send_string(parray[p].socket, tmp, 0); return retval; } PUBLIC int psend_raw_file(int p, char *dir, char *file) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; char tmp[MAX_LINE_SIZE] = { '\0' }; int num; if (dir) snprintf(fname, sizeof fname, "%s/%s", dir, file); else strlcpy(fname, file, sizeof fname); if ((fp = fopen(fname, "r")) == NULL) return -1; while ((num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1, fp)) > 0) { tmp[num] = '\0'; net_send_string(parray[p].socket, tmp, 1); } fclose(fp); return 0; } PUBLIC int psend_file(int p, char *dir, char *file) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; char tmp[MAX_LINE_SIZE] = { '\0' }; int lcount = (parray[p].d_height - 1); if (parray[p].last_file) rfree(parray[p].last_file); parray[p].last_file = NULL; parray[p].last_file_byte = 0L; if (dir) snprintf(fname, sizeof fname, "%s/%s", dir, file); else strlcpy(fname, file, sizeof fname); if ((fp = fopen(fname, "r")) == NULL) return -1; while (!feof(fp) && --lcount > 0) { if (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp)) net_send_string(parray[p].socket, tmp, 1); } if (!feof(fp)) { parray[p].last_file = xstrdup(fname); parray[p].last_file_byte = ftell(fp); pprintf(p, "Type [next] to see next page.\n"); } fclose(fp); return 0; } /* * Marsalis added on 8/27/95 so that [next] does not appear in the * logout process for those that have a short screen height. */ PUBLIC int psend_logoutfile(int p, char *dir, char *file) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; char tmp[MAX_LINE_SIZE] = { '\0' }; if (parray[p].last_file) rfree(parray[p].last_file); parray[p].last_file = NULL; parray[p].last_file_byte = 0L; if (dir) snprintf(fname, sizeof fname, "%s/%s", dir, file); else strlcpy(fname, file, sizeof fname); if ((fp = fopen(fname, "r")) == NULL) return -1; while (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp)) net_send_string(parray[p].socket, tmp, 1); fclose(fp); return 0; } PUBLIC int pmore_file(int p) { FILE *fp; char tmp[MAX_LINE_SIZE] = { '\0' }; int lcount = (parray[p].d_height - 1); if (!parray[p].last_file) { pprintf(p, "There is no more.\n"); return -1; } if ((fp = fopen(parray[p].last_file, "r")) == NULL) { pprintf(p, "File not found!\n"); return -1; } fseek(fp, parray[p].last_file_byte, SEEK_SET); while (!feof(fp) && --lcount > 0) { if (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp)) net_send_string(parray[p].socket, tmp, 1); } if (!feof(fp)) { parray[p].last_file_byte = ftell(fp); pprintf(p, "Type [next] to see next page.\n"); } else { rfree(parray[p].last_file); parray[p].last_file = NULL; parray[p].last_file_byte = 0L; } fclose(fp); return 0; } PUBLIC int psend_command(int p, char *command, char *input) { FILE *fp; char tmp[MAX_LINE_SIZE]; int num; if (input) fp = popen(command, "w"); else fp = popen(command, "r"); if (!fp) return -1; if (input) { fwrite(input, sizeof(char), strlen(input), fp); } else { while (!feof(fp)) { num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1, fp); tmp[num] = '\0'; net_send_string(parray[p].socket, tmp, 1); } } pclose(fp); return 0; } PUBLIC char * stolower(char *str) { if (!str) return NULL; for (int i = 0; str[i]; i++) { if (isupper(str[i])) str[i] = tolower(str[i]); } return str; } PUBLIC int safechar(int c) { if (c == '>' || c == '!' || c == '&' || c == '*' || c == '?' || c == '/' || c == '<' || c == '|' || c == '`' || c == '$') return 0; return 1; } PUBLIC int safestring(char *str) { if (!str) return 1; for (int i = 0; str[i]; i++) { if (!safechar(str[i])) return 0; } return 1; } PUBLIC int alphastring(char *str) { if (!str) return 1; for (int i = 0; str[i]; i++) { if (!isalpha(str[i])) return 0; } return 1; } PUBLIC int printablestring(char *str) { if (!str) return 1; for (int i = 0; str[i]; i++) { if (!isprint(str[i]) && str[i] != '\t' && str[i] != '\n') return 0; } return 1; } PUBLIC char * xstrdup(const char *str) { char *out; if (str == NULL) return NULL; const size_t size = strlen(str) + 1; out = rmalloc(size); return memcpy(out, str, size); } PUBLIC char * hms_desc(int t) { int days, hours, mins, secs; static char tstr[80]; days = t / (60 * 60 * 24); hours = (t % (60 * 60 * 24)) / (60 * 60); mins = ((t % (60 * 60 * 24)) % (60 * 60)) / 60; secs = ((t % (60 * 60 * 24)) % (60 * 60)) % 60; if (days == 0 && hours == 0 && mins == 0) { snprintf(tstr, sizeof tstr, "%d sec%s", secs, (secs == 1 ? "" : "s")); } else if (days == 0 && hours == 0) { snprintf(tstr, sizeof tstr, "%d min%s", mins, (mins == 1 ? "" : "s")); } else if (days == 0) { snprintf(tstr, sizeof tstr, "%d hr%s, %d min%s, %d sec%s", hours, (hours == 1 ? "" : "s"), mins, (mins == 1 ? "" : "s"), secs, (secs == 1 ? "" : "s")); } else { snprintf(tstr, sizeof tstr, "%d day%s, %d hour%s, %d minute%s " "and %d second%s", days, (days == 1 ? "" : "s"), hours, (hours == 1 ? "" : "s"), mins, (mins == 1 ? "" : "s"), secs, (secs == 1 ? "" : "s")); } return tstr; } PUBLIC char * hms(int t, int showhour, int showseconds, int spaces) { char tmp[10]; int h, m, s; static char tstr[20]; h = (t / 3600); t = (t % 3600); m = (t / 60); s = (t % 60); if (h || showhour) { if (spaces) snprintf(tstr, sizeof tstr, "%d : %02d", h, m); else snprintf(tstr, sizeof tstr, "%d:%02d", h, m); } else { snprintf(tstr, sizeof tstr, "%d", m); } if (showseconds) { if (spaces) snprintf(tmp, sizeof tmp, " : %02d", s); else snprintf(tmp, sizeof tmp, ":%02d", s); strlcat(tstr, tmp, sizeof tstr); } return tstr; } PRIVATE char * strtime(struct tm * stm) { static char tstr[100]; #if defined (SGI) strftime(tstr, sizeof(tstr), "%a %b %e, %H:%M %Z", stm); #else strftime(tstr, sizeof(tstr), "%a %b %e, %k:%M %Z", stm); #endif return (tstr); } PUBLIC char * fix_time(char *old_time) { char date[5] = { '\0' }; char day[5] = { '\0' }; char i; char month[5] = { '\0' }; static char new_time[20]; _Static_assert(4 < ARRAY_SIZE(day), "'day' too small"); _Static_assert(4 < ARRAY_SIZE(month), "'month' too small"); _Static_assert(4 < ARRAY_SIZE(date), "'date' too small"); if (sscanf(old_time, "%4s %4s %4s", day, month, date) != 3) warnx("%s: sscanf: too few items (%s)", __func__, old_time); if (date[2] != ',') { i = date[0]; date[0] = '0'; date[1] = i; } date[2] = '\0'; snprintf(new_time, sizeof new_time, "%s, %s %s", day, month, date); return &new_time[0]; } PUBLIC char * strltime(time_t *clock) { struct tm *stm = localtime(clock); return strtime(stm); } PUBLIC char * strgtime(time_t *clock) { struct tm *stm = gmtime(clock); return strtime(stm); } /* * This is used only for relative timing since it reports seconds * since about 5:00 pm on Feb 16, 1994. */ PUBLIC unsigned int tenth_secs(void) { struct timeval tp; struct timezone tzp; gettimeofday(&tp, &tzp); return ((tp.tv_sec - 331939277) * 10L) + (tp.tv_usec / 100000); } /* * This is to translate tenths-secs time back into 1/1/70 time in full * seconds, because vek didn't read utils.c when he programmed new * ratings. 1 sec since 1970 fits into a 32 bit int OK. */ /* * 2024-11-23 maxxe: changed the return type to 'time_t' */ PUBLIC time_t untenths(unsigned int tenths) { return (tenths / 10 + 331939277 + 0xffffffff / 10 + 1); } PUBLIC char * tenth_str(unsigned int t, int spaces) { return hms((t + 5) / 10, 0, 1, spaces); } /* * XXX: if lines in the file are greater than 1024 bytes in length, * this won't work! */ PUBLIC int truncate_file(char *file, int lines) { #define MAX_TRUNC_SIZE 100 FILE *fp; char tBuf[MAX_TRUNC_SIZE][MAX_LINE_SIZE]; int bptr = 0, trunc = 0, i; if (lines > MAX_TRUNC_SIZE) lines = MAX_TRUNC_SIZE; if ((fp = fopen(file, "r")) == NULL) return 1; while (!feof(fp)) { if (fgets(tBuf[bptr], MAX_LINE_SIZE, fp) == NULL || feof(fp)) break; if (tBuf[bptr][strlen(tBuf[bptr]) - 1] != '\n') { // Line too long fclose(fp); return -1; } if (++bptr == lines) { trunc = 1; bptr = 0; } } fclose(fp); if (trunc) { fp = fopen(file, "w"); for (i = 0; i < lines; i++) { fputs(tBuf[bptr], fp); if (++bptr == lines) bptr = 0; } fclose(fp); } return 0; } /* * XXX: If lines in the file are greater than 1024 bytes in length, * this won't work! */ PUBLIC int lines_file(char *file) { FILE *fp; char tmp[MAX_LINE_SIZE]; int lcount = 0; if ((fp = fopen(file, "r")) == NULL) return 0; while (fgets(tmp, sizeof tmp, fp) != NULL && !feof(fp)) lcount++; fclose(fp); return lcount; } PUBLIC int file_has_pname(char *fname, char *plogin) { if (!strcmp(file_wplayer(fname), plogin)) return 1; if (!strcmp(file_bplayer(fname), plogin)) return 1; return 0; } PUBLIC char * file_wplayer(char *fname) { char *ptr; static char tmp[MAX_FILENAME_SIZE]; strlcpy(tmp, fname, sizeof tmp); if ((ptr = strrchr(tmp, '-')) == NULL) return ""; *ptr = '\0'; return tmp; } PUBLIC char * file_bplayer(char *fname) { char *ptr; if ((ptr = strrchr(fname, '-')) == NULL) return ""; return ptr + 1; } /* * Hey, leave this code alone. 'a' is always in network byte order, * which is big-endian, even on a little-endian machine. I fixed this * code to handle that correctly a while ago and someone gratuitously * changed it so that it would work correctly only on a big-endian * machine (like a Sun). I have now changed it back. --mann */ PUBLIC char * dotQuad(unsigned int a) { static char tmp[20]; unsigned char *aa = (unsigned char *) &a; snprintf(tmp, sizeof tmp, "%d.%d.%d.%d", aa[0], aa[1], aa[2], aa[3]); return tmp; } PUBLIC int available_space(void) { return 100000000; /* Infinite space */ } PUBLIC int file_exists(char *fname) { FILE *fp; if ((fp = fopen(fname, "r")) == NULL) return 0; fclose(fp); return 1; } PUBLIC char * ratstr(int rat) { static char tmp[20][10]; static int on = 0; if (on == 20) on = 0; if (rat) { snprintf(tmp[on], sizeof tmp[on], "%4d", rat); } else { snprintf(tmp[on], sizeof tmp[on], "----"); } on++; return tmp[on - 1]; } PUBLIC char * ratstrii(int rat, int reg) { static char tmp[20][10]; static int on = 0; if (on == 20) on = 0; if (rat) { snprintf(tmp[on], sizeof tmp[on], "%4d", rat); } else { if (reg) { snprintf(tmp[on], sizeof tmp[on], "----"); } else { snprintf(tmp[on], sizeof tmp[on], "++++"); } } on++; return tmp[on - 1]; } /* * Fill 't_buffer' with anything matching "want*" in file tree */ PRIVATE void t_sft(char *want, struct t_tree *t) { if (t) { int cmp = strncmp(want, t->name, strlen(want)); if (cmp <= 0) // If 'want' <= this one, look left t_sft(want, t->left); if (t_buffersize && cmp == 0) { // If a match, add it to buffer t_buffersize--; *t_buffer++ = t->name; } if (cmp >= 0) // If 'want' >= this one, look right t_sft(want, t->right); } } /* * Delete file tree */ PRIVATE void t_cft(struct t_tree **t) { if (t != NULL && *t != NULL) { t_cft(&(*t)->left); t_cft(&(*t)->right); rfree((*t)->name); rfree(*t); *t = NULL; } } /* * Make file tree for dir 'd' */ PRIVATE void t_mft(struct t_dirs *d) { DIR *dirp; #ifdef USE_DIRENT struct dirent *dp; #else struct direct *dp; #endif struct t_tree **t; if ((dirp = opendir(d->name)) == NULL) { fprintf(stderr, "FICS: %s: couldn't opendir\n", __func__); return; } while ((dp = readdir(dirp))) { t = &d->files; if (dp->d_name[0] != '.') { // skip anything starting with '.' size_t size; while (*t) { if (strcmp(dp->d_name, (*t)->name) < 0) t = &(*t)->left; else t = &(*t)->right; } size = strlen(dp->d_name) + 1; *t = rmalloc(sizeof(struct t_tree)); (*t)->right = (*t)->left = NULL; (*t)->name = rmalloc(size); if (strlcpy((*t)->name, dp->d_name, size) >= size) errx(1, "%s: name too long", __func__); } } closedir(dirp); } /* * dir = directory to search * filter = what to search for * buffer = where to store pointers to matches * buffersize = how many pointers will fit inside buffer */ PUBLIC int search_directory(char *dir, char *filter, char **buffer, int buffersize) { int cmp; static char nullify = '\0'; static struct t_dirs *ramdirs = NULL; struct stat statbuf; struct t_dirs** i; t_buffer = buffer; t_buffersize = buffersize; if (!stat(dir, &statbuf)) { if (filter == NULL) // NULL becomes pointer to null string filter = &nullify; i = &ramdirs; while (*i) { // Find dir in dir tree if ((cmp = strcmp(dir, (*i)->name)) == 0) break; else if (cmp < 0) i = &(*i)->left; else i = &(*i)->right; } if (!*i) { // If dir isn't in dir tree, size_t size; // add him. size = strlen(dir) + 1; *i = rmalloc(sizeof(struct t_dirs)); (*i)->left = (*i)->right = NULL; (*i)->files = NULL; (*i)->name = rmalloc(size); if (strlcpy((*i)->name, dir, size) >= size) errx(1, "%s: dir too long", __func__); } if ((*i)->files) { // Delete any obsolete file // tree. if ((*i)->mtime != statbuf.st_mtime) t_cft(&(*i)->files); } if ((*i)->files == NULL) { // If no file tree for him, // make one. (*i)->mtime = statbuf.st_mtime; t_mft(*i); } t_sft(filter, (*i)->files); // Finally, search for matches. } return (buffersize - t_buffersize); } PUBLIC int display_directory(int p, char **buffer, int count) { // 'buffer' contains 'count' string pointers. int i; multicol *m = multicol_start(count); for (i = 0; (i < count); i++) multicol_store(m, *buffer++); multicol_pprint(m, p, 78, 1); multicol_end(m); return (i); }