#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bbs.h" #include "lua/lua.h" #include "lua/lualib.h" #include "lua/lauxlib.h" int telnet_bin_mode = 0; int mynode = 0; struct bbs_config conf; struct user_record *gUser; int gSocket; int sshBBS; int usertimeout; int timeoutpaused; time_t userlaston; char *ipaddress = NULL; void sigterm_handler2(int s) { if (mynode != 0) { disconnect("Terminated."); } dolog("Terminated..."); exit(0); } void sigint_handler(int s) { // do nothing... } void broadcast(char *mess, ...) { char json[1024]; char buffer[512]; struct sockaddr_in s; int bcast_sock; int broadcastEnable=1; int ret; if (conf.broadcast_enable && conf.broadcast_port > 1024 && conf.broadcast_port < 65536 && conf.broadcast_address != NULL) { bcast_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ret=setsockopt(bcast_sock, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)); if (ret) { dolog("broadcast: Couldn't set socket to broadcast mode"); close(bcast_sock); return; } memset(&s, 0, sizeof(struct sockaddr_in)); s.sin_family=AF_INET; s.sin_addr.s_addr = inet_addr(conf.broadcast_address); s.sin_port = htons((unsigned short)conf.broadcast_port); bind(bcast_sock, (struct sockaddr *)&s, sizeof(struct sockaddr_in)); va_list ap; va_start(ap, mess); vsnprintf(buffer, 512, mess, ap); va_end(ap); snprintf(json, 1024, "{\"System\": \"%s\", \"Program\": \"MagickaBBS\", \"Message\": \"%s\"}", conf.bbs_name, buffer); ret = sendto(bcast_sock, json, strlen(json) + 1, 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)); if (ret < 0) { dolog("broadcast: Couldn't send broadcast"); } close(bcast_sock); } } void dolog_www(char *ipaddr, char *fmt, ...) { char buffer[PATH_MAX]; struct tm time_now; time_t timen; FILE *logfptr; int mypid = getpid(); if (conf.log_path == NULL) return; timen = time(NULL); localtime_r(&timen, &time_now); snprintf(buffer, PATH_MAX, "%s/%04d%02d%02d.log", conf.log_path, time_now.tm_year + 1900, time_now.tm_mon + 1, time_now.tm_mday); logfptr = fopen(buffer, "a"); if (!logfptr) { return; } va_list ap; va_start(ap, fmt); vsnprintf(buffer, PATH_MAX, fmt, ap); va_end(ap); fprintf(logfptr, "%02d:%02d:%02d [%d][%s] %s\n", time_now.tm_hour, time_now.tm_min, time_now.tm_sec, mypid, ipaddr, buffer); fclose(logfptr); } void dolog(char *fmt, ...) { char buffer[PATH_MAX]; struct tm time_now; time_t timen; FILE *logfptr; int mypid = getpid(); if (conf.log_path == NULL) return; timen = time(NULL); localtime_r(&timen, &time_now); snprintf(buffer, PATH_MAX, "%s/%04d%02d%02d.log", conf.log_path, time_now.tm_year + 1900, time_now.tm_mon + 1, time_now.tm_mday); logfptr = fopen(buffer, "a"); if (!logfptr) { return; } va_list ap; va_start(ap, fmt); vsnprintf(buffer, PATH_MAX, fmt, ap); va_end(ap); fprintf(logfptr, "%02d:%02d:%02d [%d][%s] %s\n", time_now.tm_hour, time_now.tm_min, time_now.tm_sec, mypid, ipaddress, buffer); fclose(logfptr); } struct fido_addr *parse_fido_addr(const char *str) { if (str == NULL) { return NULL; } struct fido_addr *ret = (struct fido_addr *)malloc(sizeof(struct fido_addr)); int c; int state = 0; ret->zone = 0; ret->net = 0; ret->node = 0; ret->point = 0; for (c=0;czone = ret->zone * 10 + (str[c] - '0'); break; case 1: ret->net = ret->net * 10 + (str[c] - '0'); break; case 2: ret->node = ret->node * 10 + (str[c] - '0'); break; case 3: ret->point = ret->point * 10 + (str[c] - '0'); break; } } break; default: free(ret); return NULL; } } return ret; } void timer_handler(int signum) { if (signum == SIGALRM) { if (gUser != NULL) { gUser->timeleft--; if (gUser->timeleft <= 0) { s_printf(get_string(0)); disconnect("Out of Time"); } } if (timeoutpaused == 0) { usertimeout--; } if (usertimeout <= 0) { s_printf(get_string(1)); disconnect("Timeout"); } } } void s_printf(char *fmt, ...) { char buffer[512]; va_list ap; va_start(ap, fmt); vsnprintf(buffer, 512, fmt, ap); va_end(ap); s_putstring(buffer); } int should_convert_utf8() { if (gUser != NULL) { return gUser->codepage; } return conf.codepage; } void s_putchar(char c) { iconv_t ic; char *inbuf; char *outbuf; char *ptr1; char *ptr2; size_t inc; size_t ouc; size_t sz; int ret; if (!should_convert_utf8()) { if (sshBBS) { putchar(c); } else { ret = send(gSocket, &c, 1, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } else { ic = iconv_open("UTF-8", "CP437"); inbuf = (char *)malloc(4); outbuf = (char *)malloc(4); memset(outbuf, 0, 4); sprintf(inbuf, "%c", c); inc = 1; ouc = 4; ptr1 = outbuf; ptr2 = inbuf; sz = iconv(ic, &inbuf, &inc, &outbuf, &ouc); if (sshBBS) { fprintf(stdout, "%s", ptr1); } else { ret = send(gSocket, ptr1, outbuf - ptr1, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } iconv_close(ic); free(ptr1); free(ptr2); } } void s_putstring(char *c) { iconv_t ic; char *inbuf; char *outbuf; size_t inc; size_t ouc; size_t sz; char *ptr1; char *ptr2; int ret; if (!should_convert_utf8()) { if (sshBBS) { fprintf(stdout, "%s", c); } else { ret = send(gSocket, c, strlen(c), 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } else { ic = iconv_open("UTF-8", "CP437"); inc = strlen(c); inbuf = strdup(c); outbuf = (char *)malloc(inc * 4); memset(outbuf, 0, inc * 4); ptr1 = outbuf; ptr2 = inbuf; ouc = inc * 4; sz = iconv(ic, &inbuf, &inc, &outbuf, &ouc); if (sshBBS) { fprintf(stdout, "%s", ptr1); } else { ret = send(gSocket, ptr1, outbuf - ptr1, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } iconv_close(ic); free(ptr1); free(ptr2); } } void s_displayansi_pause(char *file, int pause) { FILE *fptr; char c; char ch; int lines = 0; char lastch = 0; fptr = fopen(file, "r"); if (!fptr) { return; } c = fgetc(fptr); while (!feof(fptr) && c != 0x1a) { if (c == '\n' && lastch != '\r') { s_putchar('\r'); } s_putchar(c); lastch = c; if (pause) { if (c == '\n') { lines++; if (lines == 24) { s_printf(get_string(223)); ch = s_getchar(); s_printf("\r\n"); switch(tolower(ch)) { case 'c': pause = 0; break; case 'n': fclose(fptr); return; default: break; } lines = 0; } } } c = fgetc(fptr); } fclose(fptr); } void s_displayansi_p(char *file) { s_displayansi_pause(file, 0); } void s_displayansi(char *file) { FILE *fptr; char c; char buffer[256]; if (strchr(file, '/') == NULL) { sprintf(buffer, "%s/%s.ans", conf.ansi_path, file); s_displayansi_pause(buffer, 0); } else { s_displayansi_pause(file, 0); } } char s_getchar() { unsigned char c; unsigned char d; int len; char iac_binary_will[] = {IAC, IAC_WILL, IAC_TRANSMIT_BINARY, '\0'}; char iac_binary_do[] = {IAC, IAC_DO, IAC_TRANSMIT_BINARY, '\0'}; char iac_binary_wont[] = {IAC, IAC_WONT, IAC_TRANSMIT_BINARY, '\0'}; char iac_binary_dont[] = {IAC, IAC_DONT, IAC_TRANSMIT_BINARY, '\0'}; int ret; do { if (sshBBS) { c = getchar(); } else { do { len = read(gSocket, &c, 1); } while (len == -1 && errno == EINTR); if (len <= 0) { disconnect("Socket Closed"); } } if (!sshBBS) { while (c == IAC) { do { len = read(gSocket, &c, 1); } while (len == -1 && errno == EINTR); if (len == 0) { disconnect("Socket Closed"); } else if (c == IAC) { usertimeout = 10; return c; } if (c == IAC_WILL || c == IAC_WONT || c == IAC_DO || c == IAC_DONT) { do { len = read(gSocket, &c, 1); } while (len == -1 && errno == EINTR); if (len <= 0) { disconnect("Socket Closed"); } switch (c) { case IAC_WILL: if (d == 0) { if (telnet_bin_mode != 1) { telnet_bin_mode = 1; ret = send(gSocket, iac_binary_do, 3, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } break; case IAC_WONT: if (d == 0) { if (telnet_bin_mode != 0) { telnet_bin_mode = 0; ret = send(gSocket, iac_binary_dont, 3, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } break; case IAC_DO: if (d == 0) { if (telnet_bin_mode != 1) { telnet_bin_mode = 1; ret = send(gSocket, iac_binary_will, 3, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } break; case IAC_DONT: if (d == 0) { if (telnet_bin_mode != 0) { telnet_bin_mode = 0; ret = send(gSocket, iac_binary_wont, 3, 0); if (ret == -1) { if (errno == ECONNRESET) { disconnect("Disconnected"); } } } } break; } } else if (c == 250) { do { do { len = read(gSocket, &c, 1); } while (len == -1 && errno == EINTR); if (len <= 0) { disconnect("Socket Closed"); } } while(c != 240); } do { len = read(gSocket, &c, 1); } while (len == -1 && errno == EINTR); if (len <= 0) { disconnect("Socket Closed"); } } } } while (c == '\n' || c == '\0'); usertimeout = 10; return (char)c; } char s_getc() { char c = s_getchar(); s_putchar(c); return (char)c; } void s_readstring_inject(char *buffer, int max, char *inject) { int i; char c; memset(buffer, 0, max); if (strlen(inject) > max) { return; } strcpy(buffer, inject); s_printf("%s", inject); for (i=strlen(buffer);i 0) { buffer[i-1] = '\0'; i -= 2; s_printf("\e[D \e[D"); continue; } else if (c == '\b' || c == 127) { i -= 1; continue; } else if (c == 27) { c = s_getchar(); if (c == 91) { c = s_getchar(); } i -= 1; continue; } if (c == '\n' || c == '\r') { return; } s_putchar(c); buffer[i] = c; buffer[i+1] = '\0'; } } void s_readstring(char *buffer, int max) { int i; char c; memset(buffer, 0, max); for (i=0;i 0) { buffer[i-1] = '\0'; i -= 2; s_printf("\e[D \e[D"); continue; } else if (c == '\b' || c == 127) { i -= 1; continue; } else if (c == 27) { c = s_getchar(); if (c == 91) { c = s_getchar(); } i -= 1; continue; } if (c == '\n' || c == '\r') { return; } s_putchar(c); buffer[i] = c; buffer[i+1] = '\0'; } } void s_readpass(char *buffer, int max) { int i; char c; for (i=0;i 0) { buffer[i-1] = '\0'; i-=2; s_printf("\e[D \e[D"); continue; } else if (c == '\b' || c == 127) { i -= 1; continue; } else if (c == 27) { c = s_getchar(); if (c == 91) { c = s_getchar(); } i -= 1; continue; } if (c == '\n' || c == '\r') { return; } s_putchar('*'); buffer[i] = c; buffer[i+1] = '\0'; } } void exit_bbs() { char buffer[1024]; snprintf(buffer, 1024, "%s/nodeinuse.%d", conf.bbs_path, mynode); remove(buffer); } void disconnect(char *calledby) { if (gUser != NULL) { save_user(gUser); } dolog("Node %d disconnected (%s)", mynode, calledby); if (!sshBBS) { close(gSocket); } exit(0); } void record_last10_callers(struct user_record *user) { struct last10_callers new_entry; struct last10_callers callers[10]; int i,j; FILE *fptr = fopen("last10v2.dat", "rb"); if (fptr != NULL) { for (i=0;i<10;i++) { if (fread(&callers[i], sizeof(struct last10_callers), 1, fptr) < 1) { break; } } fclose(fptr); } else { i = 0; } if (strcasecmp(conf.sysop_name, user->loginname) != 0 ) { memset(&new_entry, 0, sizeof(struct last10_callers)); strcpy(new_entry.name, user->loginname); strcpy(new_entry.location, user->location); new_entry.time = time(NULL); new_entry.calls = user->timeson; if (i == 10) { j = 1; } else { j = 0; } fptr = fopen("last10v2.dat", "wb"); for (;jsec_level >= conf.automsgwritelvl) { timen = time(NULL); localtime_r(&timen, &timenow); sprintf(automsg, get_string(15), gUser->loginname, asctime(&timenow)); automsg[strlen(automsg) - 1] = '\r'; automsg[strlen(automsg)] = '\n'; s_printf(get_string(16)); for (i=0;i<4;i++) { s_printf("\r\n%d: ", i); s_readstring(buffer, 75); strcat(automsg, buffer); strcat(automsg, "\r\n"); } fptr = fopen("automessage.txt", "w"); if (fptr) { fwrite(automsg, strlen(automsg), 1, fptr); fclose(fptr); } else { dolog("Unable to open automessage.txt for writing"); } } } void automessage_display() { struct stat s; FILE *fptr; char buffer[90]; int i; s_printf("\r\n\r\n"); if (stat("automessage.txt", &s) == 0) { fptr = fopen("automessage.txt", "r"); if (fptr) { for (i=0;i<5;i++) { memset(buffer, 0, 90); fgets(buffer, 88, fptr); buffer[strlen(buffer) - 1] = '\r'; buffer[strlen(buffer)] = '\n'; s_printf(buffer); } fclose(fptr); } else { dolog("Error opening automessage.txt"); } } else { s_printf(get_string(17)); } s_printf(get_string(6)); s_getc(); } void automessage() { char c; s_printf(get_string(275)); c = s_getchar(); s_printf("\r\n"); switch(tolower(c)) { case 'v': automessage_display(); break; case 'u': automessage_write(); break; default: break; } return; } void runbbs_real(int socket, char *ip, int ssh) { char buffer[PATH_MAX]; char password[17]; struct stat s; FILE *nodefile; int i; char iac_echo[] = {IAC, IAC_WILL, IAC_ECHO, '\0'}; char iac_sga[] = {IAC, IAC_WILL, IAC_SUPPRESS_GO_AHEAD, '\0'}; struct user_record *user; struct tm thetime; struct tm oldtime; time_t now; struct itimerval itime; struct sigaction sa; struct sigaction st; lua_State *L; int do_internal_login = 0; int usernotfound; int tries; int fno; atexit(exit_bbs); usertimeout = 10; timeoutpaused = 0; memset (&sa, 0, sizeof (sa)); sa.sa_handler = &timer_handler; sa.sa_flags = SA_RESTART; sigaction (SIGALRM, &sa, 0); itime.it_interval.tv_sec = 60; itime.it_interval.tv_usec = 0; itime.it_value.tv_sec = 60; itime.it_value.tv_usec = 0; setitimer (ITIMER_REAL, &itime, 0); ipaddress = ip; st.sa_handler = sigterm_handler2; sigemptyset(&st.sa_mask); st.sa_flags = SA_SIGINFO; if (sigaction((ssh ? SIGHUP : SIGTERM), &st, NULL) == -1) { dolog("Failed to setup sigterm handler."); exit(1); } if (sigaction(SIGPIPE, &st, NULL) == -1) { dolog("Failed to setup sigpipe handler."); exit(1); } gSocket = socket; if (!ssh) { gUser = NULL; sshBBS = 0; if (send(socket, iac_echo, 3, 0) != 3) { dolog("Failed to send iac_echo"); exit(0); } if (send(socket, iac_sga, 3, 0) != 3) { dolog("Failed to send iac_sga"); exit(0); } } else { sshBBS = 1; } s_printf("Magicka BBS v%d.%d (%s), Loading...\r\n", VERSION_MAJOR, VERSION_MINOR, VERSION_STR); // find out which node we are for (i=1;i<=conf.nodes;i++) { sprintf(buffer, "%s/nodeinuse.%d", conf.bbs_path, i); if (stat(buffer, &s) != 0) { mynode = i; fno = open(buffer, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fno == -1) { dolog("Error opening nodefile!"); continue; } write(fno, "UNKNOWN", 7); close(fno); break; } } if (mynode == 0) { s_printf(get_string(18)); if (!ssh) { close(socket); } exit(1); } dolog("Incoming %s connection on node %d", (ssh ? "SSH" : "Telnet"), mynode); s_displayansi("issue"); tries = 0; if (!ssh) { tryagain: s_printf(get_string(19)); s_printf(get_string(20)); s_readstring(buffer, 25); usernotfound = 0; if (strcasecmp(buffer, "new") == 0) { usernotfound = 1; } else if (check_user(buffer)) { s_printf(get_string(203)); goto tryagain; } if (usernotfound) { dolog("New user on node %d", mynode); user = new_user(); gUser = user; } else { s_printf(get_string(21)); s_readpass(password, 16); user = check_user_pass(buffer, password); if (user == NULL) { if (tries == 3) { s_printf(get_string(22)); disconnect("Incorrect Login"); } else { tries++; s_printf(get_string(22)); goto tryagain; } } gUser = user; for (i=1;i<=conf.nodes;i++) { snprintf(buffer, PATH_MAX, "%s/nodeinuse.%d", conf.bbs_path, i); if (stat(buffer, &s) == 0) { nodefile = fopen(buffer, "r"); if (!nodefile) { dolog("Error opening nodefile!"); disconnect("Error opening nodefile!"); } fgets(buffer, 256, nodefile); if (strcasecmp(user->loginname, buffer) == 0) { fclose(nodefile); s_printf(get_string(23)); disconnect("Already Logged in"); } fclose(nodefile); } } } } else { if (gUser != NULL) { user = gUser; s_printf(get_string(24), gUser->loginname); s_getc(); for (i=1;i<=conf.nodes;i++) { snprintf(buffer, PATH_MAX, "%s/nodeinuse.%d", conf.bbs_path, i); if (stat(buffer, &s) == 0) { nodefile = fopen(buffer, "r"); if (!nodefile) { dolog("Error opening nodefile!"); disconnect("Error opening nodefile!"); } fgets(buffer, 256, nodefile); if (strcasecmp(user->loginname, buffer) == 0) { fclose(nodefile); s_printf(get_string(23)); disconnect("Already Logged in"); } fclose(nodefile); } } } else { s_printf(get_string(25), conf.bbs_name); s_getc(); gUser = new_user(); user = gUser; } } snprintf(buffer, PATH_MAX, "%s/nodeinuse.%d", conf.bbs_path, mynode); nodefile = fopen(buffer, "w"); if (!nodefile) { dolog("Error opening nodefile!"); close(socket); exit(1); } fputs(user->loginname, nodefile); fclose(nodefile); snprintf(buffer, PATH_MAX, "%s/node%d/nodemsg.txt", conf.bbs_path, mynode); if (stat(buffer, &s) == 0) { unlink(buffer); } snprintf(buffer, PATH_MAX, "%s/node%d/lua/", conf.bbs_path, mynode); if (stat(buffer, &s) == 0) { recursive_delete(buffer); } #if defined(ENABLE_WWW) www_expire_old_links(); #endif // do post-login dolog("%s logged in, on node %d", user->loginname, mynode); broadcast("%s logged in, on node %d", user->loginname, mynode); // check time left now = time(NULL); localtime_r(&now, &thetime); localtime_r(&user->laston, &oldtime); userlaston = user->laston; if (thetime.tm_mday != oldtime.tm_mday || thetime.tm_mon != oldtime.tm_mon || thetime.tm_year != oldtime.tm_year) { user->timeleft = user->sec_info->timeperday; user->laston = now; save_user(user); } user->timeson++; if (conf.script_path != NULL) { snprintf(buffer, PATH_MAX, "%s/login_stanza.lua", conf.script_path); if (stat(buffer, &s) == 0) { L = luaL_newstate(); luaL_openlibs(L); lua_push_cfunctions(L); luaL_dofile(L, buffer); lua_close(L); do_internal_login = 0; } else { do_internal_login = 1; } } else { do_internal_login = 1; } if (do_internal_login == 1) { // bulletins display_bulletins(); blog_display(); // display info display_info(); display_last10_callers(user); // check email i = mail_getemailcount(user); if (i > 0) { s_printf(get_string(26), i); } else { s_printf(get_string(27)); } mail_scan(user); file_scan(); automessage_display(); } record_last10_callers(user); // main menu menu_system(conf.root_menu); do_logout(); dolog("%s is logging out, on node %d", user->loginname, mynode); broadcast("%s is logging out, on node %d", user->loginname, mynode); disconnect("Log out"); } void do_logout() { char buffer[PATH_MAX]; struct stat s; lua_State *L; int ret = 0; char c; int result; int do_internal_logout = 1; if (conf.script_path != NULL) { snprintf(buffer, PATH_MAX, "%s/logout_stanza.lua", conf.script_path); if (stat(buffer, &s) == 0) { L = luaL_newstate(); luaL_openlibs(L); lua_push_cfunctions(L); luaL_loadfile(L, buffer); result = lua_pcall(L, 0, 1, 0); if (result) { dolog("Failed to run script: %s", lua_tostring(L, -1)); do_internal_logout = 1; lua_close(L); } else { do_internal_logout = 0; } } } if (do_internal_logout == 1) { s_displayansi("goodbye"); } else { lua_getglobal(L, "logout"); result = lua_pcall(L, 0, 0, 0); if (result) { dolog("Failed to run script: %s", lua_tostring(L, -1)); } lua_close(L); } } void runbbs(int socket, char *ip) { runbbs_real(socket, ip, 0); } void runbbs_ssh(char *ip) { struct sigaction si; setbuf(stdin, NULL); setbuf(stdout, NULL); si.sa_handler = sigint_handler; sigemptyset(&si.sa_mask); if (sigaction(SIGINT, &si, NULL) == -1) { dolog("Failed to setup sigint handler."); exit(1); } runbbs_real(-1, ip, 1); } int recursive_delete(const char *dir) { int ret = 0; FTS *ftsp = NULL; FTSENT *curr; char *files[] = { (char *) dir, NULL }; ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL); if (!ftsp) { dolog("%s: fts_open failed: %s", dir, strerror(errno)); ret = -1; goto finish; } while ((curr = fts_read(ftsp))) { switch (curr->fts_info) { case FTS_NS: case FTS_DNR: case FTS_ERR: dolog("%s: fts_read error: %s", curr->fts_accpath, strerror(curr->fts_errno)); break; case FTS_DC: case FTS_DOT: case FTS_NSOK: break; case FTS_D: break; case FTS_DP: case FTS_F: case FTS_SL: case FTS_SLNONE: case FTS_DEFAULT: if (remove(curr->fts_accpath) < 0) { dolog("%s: Failed to remove: %s", curr->fts_path, strerror(errno)); ret = -1; } break; } } finish: if (ftsp) { fts_close(ftsp); } return ret; } int copy_file(char *src, char *dest) { FILE *src_file; FILE *dest_file; char c; src_file = fopen(src, "rb"); if (!src_file) { return -1; } dest_file = fopen(dest, "wb"); if (!dest_file) { fclose(src_file); return -1; } while(1) { c = fgetc(src_file); if (!feof(src_file)) { fputc(c, dest_file); } else { break; } } fclose(src_file); fclose(dest_file); return 0; } char *str_replace(const char *str, const char *from, const char *to) { /* Adjust each of the below values to suit your needs. */ /* Increment positions cache size initially by this number. */ size_t cache_sz_inc = 16; /* Thereafter, each time capacity needs to be increased, * multiply the increment by this factor. */ const size_t cache_sz_inc_factor = 3; /* But never increment capacity by more than this number. */ const size_t cache_sz_inc_max = 1048576; char *pret, *ret = NULL; const char *pstr2, *pstr = str; size_t i, count = 0; uintptr_t *pos_cache_tmp, *pos_cache = NULL; size_t cache_sz = 0; size_t cpylen, orglen, retlen, tolen, fromlen = strlen(from); /* Find all matches and cache their positions. */ while ((pstr2 = strstr(pstr, from)) != NULL) { count++; /* Increase the cache size when necessary. */ if (cache_sz < count) { cache_sz += cache_sz_inc; pos_cache_tmp = realloc(pos_cache, sizeof(*pos_cache) * cache_sz); if (pos_cache_tmp == NULL) { goto end_repl_str; } else pos_cache = pos_cache_tmp; cache_sz_inc *= cache_sz_inc_factor; if (cache_sz_inc > cache_sz_inc_max) { cache_sz_inc = cache_sz_inc_max; } } pos_cache[count-1] = pstr2 - str; pstr = pstr2 + fromlen; } orglen = pstr - str + strlen(pstr); /* Allocate memory for the post-replacement string. */ if (count > 0) { tolen = strlen(to); retlen = orglen + (tolen - fromlen) * count; } else retlen = orglen; ret = malloc(retlen + 1); if (ret == NULL) { goto end_repl_str; } if (count == 0) { /* If no matches, then just duplicate the string. */ strcpy(ret, str); } else { /* Otherwise, duplicate the string whilst performing * the replacements using the position cache. */ pret = ret; memcpy(pret, str, pos_cache[0]); pret += pos_cache[0]; for (i = 0; i < count; i++) { memcpy(pret, to, tolen); pret += tolen; pstr = str + pos_cache[i] + fromlen; cpylen = (i == count-1 ? orglen : pos_cache[i+1]) - pos_cache[i] - fromlen; memcpy(pret, pstr, cpylen); pret += cpylen; } ret[retlen] = '\0'; } end_repl_str: /* Free the cache and return the post-replacement string, * which will be NULL in the event of an error. */ free(pos_cache); return ret; }