/* Apache Installer */ /* * 26/06/97 PCS 1.000 Initial version * 22/02/98 PCS 1.001 Used the excellent NTemacs to apply proper formating * 04/05/98 PCS 1.002 Copy conf files to *.conf.default, then to *.conf * 16/02/99 PCS 1.003 Add logging to "install.log" in the installed directory */ #define VERSION ( "1.003 " __DATE__ " " __TIME__ ) #include #include #include #include #include #include "conf.h" #include "ap.h" #ifdef strftime #undef strftime #endif #define AP_WIN32ERROR 1 /* Global to store the instance handle */ HINSTANCE hInstance = NULL; static char *szLogFilename = NULL; static FILE *fpLog = NULL; void LogMessage(char *fmt, ...) { char buf[4000]; va_list ap; struct tm *tms; time_t nowtime; char *bp = buf; int rv; int free = sizeof(buf); if (!fpLog) { return; } nowtime = time(NULL); tms = localtime(&nowtime); rv = strftime(bp, free, "%c", tms); bp += rv; free -= rv; if (free) { *bp++ = ' '; free--; } va_start(ap, fmt); rv = ap_vsnprintf(bp, free, fmt, ap); va_end(ap); free -= rv; fprintf(fpLog, "%s\n", buf); } /* * MessageBox_error() is a helper function to display an error in a * message box, optionally including a Win32 error message. If * the "opt" argument is value AP_WIN32ERROR then get the last Win32 * error (with GetLastError()) and add it on to the end of * the output string. The output string is given as a printf-format * and replacement arguments. The hWnd, title and mb_opt fields are * passed on to the Win32 MessageBox() call. */ int MessageBox_error(HWND hWnd, int opt, char *title, int mb_opt, char *fmt, ...) { char buf[1000]; va_list ap; int free = sizeof(buf); /* Number of bytes free in the buffer */ int rv; char *p; va_start(ap, fmt); rv = ap_vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); free -= rv; if (opt & AP_WIN32ERROR && free > 3) { /* We checked in the "if" that we have enough space in buf for * at least three extra characters. */ p = buf + strlen(buf); *p++ = '\r'; *p++ = '\r'; *p++ = '('; free -= 3; /* NB: buf is now not null terminated */ /* Now put the error message straight into buf. This function * takes the free buffer size as the 6th argument. */ rv = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, p, free, NULL); if (rv == 0) { /* FormatMessage failed, so get rid of the "\r\r(" we just placed * in the buffer, since there is no system error message. */ p -= 3; *p = '\0'; free += 3; } else { free -= rv; p += rv; /* Strip any trailing \r or \n characters to make it look nice on * the screen. */ while (*(p-1) == '\r' || *(p-1) == '\n') p--, free++; *p = '\0'; /* Append a trailing ) */ if (free >= 1) { *p++ = ')'; *p++ = '\0'; } } } for (p = buf; *p; p++) { if (*p == '\n' || *p == '\r') { *p = ' '; } } LogMessage("MSG %s", buf); return MessageBox(hWnd, buf, title, mb_opt); } int OpenLog(HWND hwnd, char *dir, char *fn) { szLogFilename = malloc(strlen(dir) + 1 + strlen(fn) + 1); sprintf(szLogFilename, "%s\\%s", dir, fn); if ((fpLog = fopen(szLogFilename, "a+")) == NULL) { MessageBox_error(hwnd, AP_WIN32ERROR, "Installation Problem", MB_OK | MB_ICONSTOP, "Cannot open log file %s", szLogFilename); return -1; } return 0; } void CloseLog(void) { if (fpLog) { fclose(fpLog); } } /* * The next few functions handle expanding the @@ServerRoot@@ type * sequences found in the distribution files. The main entry point * is expandFile(), which is given a file to expand and the filename * to write the expanded file it. It reads a line at a time, and * if the line includes an "@@" sequence, calls expandLine() to * expand the sequences. * * expandLine() looks for @@ sequences in the line, and when it finds * one, looks for a corresponding entry in the replaceTable[]. If it * finds one it writes the replacement value from the table into * an output string. * * The helper function appendText() is used when expanding strings. It * is called to copy text into an output buffer. If the output buffer * is not big enough, it is expanded. This function also ensures that * the output buffer is null terminated after each append operation. * * A table is used of values to replace, rather than just hardcoding * the functionality, so we could replace additional values in the * future. We also take care to ensure that the expanded line can be * arbitrary length (though at the moment the lines read from the * configuration files can only be up to 2000 characters). */ /* * Table of items to replace. The "value" elements are filled in at runtime * by FillInReplaceTable(). Note that the "tmpl" element is case * sensitive. */ typedef struct { char *tmpl; char *value; } REPLACEITEM; typedef REPLACEITEM *REPLACETABLE; REPLACEITEM replaceHttpd[] = { { "@@ServerRoot@@", NULL }, /* ServerRoot directory (i.e. install dir) */ { NULL, NULL } }; /* * A relatively intelligent version of strcat, that expands the destination * buffer if needed. * * On entry, ppBuf points to the output buffer, pnPos points to the offset * of the current position within that buffer, and pnSize points to the * current size of *ppBuf. pSrc points to the string to copy into the buffer, * and nToCopy gives the number of characters to copy from pSrc. * * On exit, *ppBuf, *pnPos and *pnSize may have been updated if the output * buffer needed to be expanded. The output buffer will be null terminated. * Returns 0 on success, -1 on error. Does not report errors to the user. */ int appendText(char **ppBuf, int *pnPos, int *pnSize, char *pSrc, int nToCopy) { char *pBuf = *ppBuf; /* output buffer */ int nPos = *pnPos; /* current offset into pBuf */ int nSize = *pnSize; /* current size of pBuf */ while (nPos + nToCopy >= nSize) { /* Not enough space, double size of output buffer. Note we use * >= not > so that we have enough space for the NULL character * in the output buffer */ char *pBufNew; pBufNew = realloc(pBuf, nSize * 2); if (!pBufNew) return -1; nSize *= 2; /* Update the max size and buffer pointer */ *pnSize = nSize; *ppBuf = pBuf = pBufNew; } /* Ok, now we have enough space, copy the stuff */ strncpy(pBuf+nPos, pSrc, nToCopy); nPos += nToCopy; *pnPos = nPos; /* update current position */ pBuf[nPos] = '\0'; /* append trailing NULL */ return 0; } /* * Expand all the sequences in an input line. Returns a pointer to the * expanded line. The caller should free the returned data. * The replaceTable argument is a table of sequences to expand. * * Returns NULL on error. Does not report errors to the user. */ char *expandLine(char *in, REPLACETABLE replaceTable) { REPLACEITEM *item; char *pos; /* current position in input buffer */ char *outbuf; /* output buffer */ int outbuf_size; /* current size of output buffer */ int outbuf_position; /* current position in output buffer */ char *start; /* position to copy from in input buffer */ /* Create an initial output buffer. Guess that twice the input size * is a good length, after expansion. Don't worry if we need more * though, appendText() will expand as needed. */ outbuf_size = strlen(in) * 2; outbuf_position = 0; outbuf = malloc(outbuf_size); if (!outbuf) return NULL; start = in; /* mark the start of uncopied data */ pos = in; /* current position in input buffer */ while (1) { /* Look for '@@' sequence, or end of input */ if (*pos && !(*pos == '@' && *(pos+1) == '@')) { pos++; continue; } if (!*pos) { /* End of input string, copy the uncopied data */ if (appendText(&outbuf, &outbuf_position, &outbuf_size, start, pos-start) < 0) { return NULL; } break; } /* Found first @ of a possible token to replace. Look for end * of the token */ for (item = replaceTable; item->tmpl; ++item) { if (!strncmp(pos, item->tmpl, strlen(item->tmpl))) break; } if (item->tmpl) { /* Found match. First copy the uncopied data from the input * buffer (start through to pos-1), then copy the expanded * value. */ if (appendText(&outbuf, &outbuf_position, &outbuf_size, start, pos-start) < 0) { return NULL; } if (appendText(&outbuf, &outbuf_position, &outbuf_size, item->value, strlen(item->value)) < 0) { return NULL; } /* Update current position to skip over the input buffer * @@...@@ sequence, and update the "start" pointer to uncopied * data */ pos += strlen(item->tmpl); start = pos; } else { /* The sequence did not exist in the replace table, so copy * it as-is to the output. */ pos++; } } return outbuf; } /* * Some options to determine how we copy a file. Apart from OPT_NONE, these should * be OR'able */ typedef enum { OPT_NONE = 0, OPT_OVERWRITE = 1, /* Always overwrite destination file */ OPT_EXPAND = 2, /* Expand any @@...@@ tokens in replaceHttpd */ OPT_DELETESOURCE = 4, /* Delete the source file after the copy */ OPT_SILENT = 8, /* Don't tell use about failures */ } options_t; /* * Copy a file, expanding sequences from the replaceTable argument. * Returns 0 on success, -1 on error. Reports errors to user. */ #define MAX_INPUT_LINE 2000 int WINAPI ExpandConfFile(HWND hwnd, LPSTR szInst, LPSTR szinFile, LPSTR szoutFile, REPLACETABLE replaceTable, options_t options) { char inFile[_MAX_PATH]; char outFile[_MAX_PATH]; char inbuf[MAX_INPUT_LINE]; FILE *infp; FILE *outfp; ap_snprintf(inFile, sizeof(inFile), "%s\\%s", szInst, szinFile); ap_snprintf(outFile, sizeof(outFile), "%s\\%s", szInst, szoutFile); if (!(infp = fopen(inFile, "r"))) { MessageBox_error(hwnd, AP_WIN32ERROR, "Installation Problem", MB_OK | MB_ICONSTOP, "Cannot read file %s", inFile); return -1; } if (! (options & OPT_OVERWRITE)) { /* Overwrite not allowed if file does not exist */ if ((outfp = fopen(outFile, "r"))) { if (! (options & OPT_SILENT)) { MessageBox_error(hwnd, 0, "File not overwritten", MB_OK | MB_ICONWARNING, "Preserving existing file %s.\r\r" "The new version of this file has been left in %s", outFile, inFile); } fclose(outfp); fclose(infp); return 0; } /* To get here, output file does not exist */ } if (!(outfp = fopen(outFile, "w"))) { MessageBox_error(hwnd, AP_WIN32ERROR, "Installation Problem", MB_OK | MB_ICONSTOP, "Cannot write to file %s", outFile); fclose(infp); return -1; } while (fgets(inbuf, MAX_INPUT_LINE, infp)) { char *pos; char *outbuf; /* Quickly look to see if this line contains any * @@ tokens. If it doesn't, we don't need to bother * called expandLine() or taking a copy of the input * buffer. */ if (options & OPT_EXPAND) { for (pos = inbuf; *pos; ++pos) if (*pos == '@' && *(pos+1) == '@') break; } if (options & OPT_EXPAND && *pos) { /* The input line contains at least one '@@' sequence, so * call expandLine() to expand any sequences we know about. */ outbuf = expandLine(inbuf, replaceTable); if (outbuf == NULL) { fclose(infp); fclose(outfp); MessageBox_error(hwnd, 0, "Installation Problem", MB_OK|MB_ICONSTOP, "An error occurred during installation"); return -1; } } else { outbuf = NULL; } /* If outbuf is NULL, we did not need to expand sequences, so * just output the contents of the input buffer. */ fwrite(outbuf ? outbuf : inbuf, 1, strlen(outbuf ? outbuf : inbuf), outfp); if (outbuf) free(outbuf); } fclose(infp); fclose(outfp); LogMessage("COPY: expanded %s to %s", inFile, outFile); if (options & OPT_DELETESOURCE) { unlink(inFile); LogMessage("COPY: deleted file %s", inFile); } return 0; } int FillInReplaceTable(HWND hwnd, REPLACETABLE table, char *szInst) { REPLACEITEM *item; for (item = table; item->tmpl; ++item) { if (!strcmp(item->tmpl, "@@ServerRoot@@")) { char *p; #if NEED_SHORT_PATHS int len; len = GetShortPathName(szInst, NULL, 0); if (len > 0) { item->value = (char*)malloc(len+1); GetShortPathName(szInst, item->value, len); } #else if ((item->value = strdup(szInst)) == NULL) return -1; #endif for (p = item->value; *p; p++) if (*p == '\\') *p = '/'; LogMessage("FillInReplaceTable tmpl=%s value=%s", item->tmpl, item->value); continue; } #if NEED_FQDN if (!strcmp(item->tmpl, "FQDN")) { item->value = GetHostName(hwnd); continue; } #endif } return 0; } /* * actionTable[] contains things we do when this DLL is called by InstallShield * during the install. It is like a simple script, without us having to * worry about parsing, error checking, etc. * * Each item in the table is of type ACTIONITEM. The first element is the action * to perform (e.g. CMD_COPY). The second and third elements are filenames * (e.g. for CMD_COPY, the first filename is the source and the second filename * is the destination). The final element of ACTIONITEM is a set of options * which apply to the current "command". For example, OPT_EXPAND on a CMD_COPY * line, tells the copy function to expand @@ServerRoot@@ tokens found in the * source file. * * The contents of this table are performed in order, top to bottom. This lets * us expand the files to the *.conf.default names, then copy to *.conf only * if the corresponding *.conf file does not already exist. If it does exist, * it is not overwritten. * * Return 1 on success, 0 on error. */ typedef enum { CMD_COPY = 0, CMD_RMDIR, CMD_RM, CMD_END } cmd_t; typedef struct { cmd_t command; char *in; char *out; options_t options; } ACTIONITEM; typedef ACTIONITEM *ACTIONTABLE; ACTIONITEM actionTable[] = { /* * Installation of the configuraton files. These are installed into the ".tmp" * directory by the installer. We first move them to conf\*.default (overwriting * any *.default file from a previous install). The *.conf-dist-win files * are also expanded for any @@...@@ tokens. Then we copy the conf\*.default * file to corresponding conf\* file, unless that would overwrite an existing file. */ { CMD_COPY, ".tmp\\mime.types", "conf\\mime.types.default", OPT_OVERWRITE|OPT_DELETESOURCE }, { CMD_COPY, ".tmp\\magic", "conf\\magic.default", OPT_OVERWRITE|OPT_DELETESOURCE }, { CMD_COPY, ".tmp\\httpd.conf-dist-win", "conf\\httpd.conf.default", OPT_OVERWRITE|OPT_EXPAND|OPT_DELETESOURCE }, { CMD_COPY, ".tmp\\srm.conf-dist-win", "conf\\srm.conf.default", OPT_OVERWRITE|OPT_EXPAND|OPT_DELETESOURCE }, { CMD_COPY, ".tmp\\access.conf-dist-win", "conf\\access.conf.default", OPT_OVERWRITE|OPT_EXPAND|OPT_DELETESOURCE }, /* Now copy to the 'live' files, unless they already exist */ { CMD_COPY, "conf\\httpd.conf.default", "conf\\httpd.conf", OPT_NONE }, { CMD_COPY, "conf\\srm.conf.default", "conf\\srm.conf", OPT_NONE }, { CMD_COPY, "conf\\access.conf.default", "conf\\access.conf", OPT_NONE }, { CMD_COPY, "conf\\magic.default", "conf\\magic", OPT_NONE }, { CMD_COPY, "conf\\mime.types.default", "conf\\mime.types", OPT_NONE }, { CMD_COPY, ".tmp\\highperformance.conf-dist", "conf\\highperformance.conf-dist", OPT_EXPAND|OPT_OVERWRITE|OPT_DELETESOURCE }, { CMD_RMDIR, ".tmp", NULL }, { CMD_END, NULL, NULL, OPT_NONE } }; /* * BeforeExit() is the DLL call from InstallShield. The arguments and * return value as defined by the installer. We are only interested * in the install directory, szInst. Return 0 on error and 1 on * success (!?). */ CHAR WINAPI BeforeExit(HWND hwnd, LPSTR szSrcDir, LPSTR szSupport, LPSTR szInst, LPSTR szRes) { ACTIONITEM *pactionItem; int end = 0; OpenLog(hwnd, szInst, "install.log"); LogMessage("STARTED %s", VERSION); LogMessage("src=%s support=%s inst=%s", szSrcDir, szSupport, szInst); FillInReplaceTable(hwnd, replaceHttpd, szInst); pactionItem = actionTable; while (!end) { LogMessage("command=%d 1in=%s out=%s options=%d", pactionItem->command, pactionItem->in ? pactionItem->in : "NULL", pactionItem->out ? pactionItem->out : "NULL", pactionItem->options); switch(pactionItem->command) { case CMD_END: end = 1; break; case CMD_COPY: if (ExpandConfFile(hwnd, szInst, pactionItem->in, pactionItem->out, replaceHttpd, pactionItem->options) < 0) { /* Error has already been reported to the user */ return 0; } break; case CMD_RM: { char inFile[MAX_INPUT_LINE]; ap_snprintf(inFile, sizeof(inFile), "%s\\%s", szInst, pactionItem->in); if (unlink(inFile) < 0 && !(pactionItem->options & OPT_SILENT)) { MessageBox_error(hwnd, AP_WIN32ERROR, "Error during configuration", MB_ICONHAND, "Could not remove file %s", inFile); return 0; } LogMessage("RM: deleted file %s", inFile); break; } case CMD_RMDIR: { char inFile[MAX_INPUT_LINE]; ap_snprintf(inFile, sizeof(inFile), "%s\\%s", szInst, pactionItem->in); if (rmdir(inFile) < 0) { MessageBox_error(hwnd, AP_WIN32ERROR, "Error during configuration", MB_ICONHAND, "Could not delete temporary directory %s", inFile); return 0; } LogMessage("RMDIR: deleted directory %s", inFile); break; } default: MessageBox_error(hwnd, 0, "Error during configuration", MB_ICONHAND, "An error has occurred during configuration\r" "(Error: unknown command %d)", (int)pactionItem->command); end = 1; break; } pactionItem++; } LogMessage("FINISHED OK"); CloseLog(); return 1; } BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved) { if (fdwReason == DLL_PROCESS_ATTACH) hInstance = hInstDLL; return TRUE; }