/* * tools.c * * This file is part of msmtp, an SMTP client. * * Copyright (C) 2004, 2005, 2006, 2007 * Martin Lambers * * 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef W32_NATIVE # include # include # include # include # include #else # include #endif #include "gettext.h" #include "xalloc.h" #include "tools.h" /* * get_prgname() * * see tools.h */ const char *get_prgname(const char *argv0) { const char *prgname; if (argv0) { prgname = strrchr(argv0, PATH_SEP); if (!prgname) { prgname = argv0; } else { prgname++; } } else { prgname = ""; } return prgname; } /* * get_sysconfdir() * * see tools.h */ char *get_sysconfdir(void) { #ifdef W32_NATIVE BYTE sysconfdir[MAX_PATH + 1]; HKEY hkey; DWORD len; DWORD type; long l; l = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" "Shell Folders", 0, KEY_READ, &hkey); if (l != ERROR_SUCCESS) { return xstrdup("C:"); } len = MAX_PATH; l = RegQueryValueEx(hkey, "Common AppData", NULL, &type, sysconfdir, &len); if (l != ERROR_SUCCESS || len >= MAX_PATH) { if (l != ERROR_SUCCESS || len >= MAX_PATH) { return xstrdup("C:"); } } RegCloseKey(hkey); return xstrdup((char *)sysconfdir); #else /* UNIX or DJGPP */ #ifdef SYSCONFDIR return xstrdup(SYSCONFDIR); #else return xstrdup("/etc"); #endif #endif } /* * get_username() * * see tools.h */ char *get_username(void) { char *username; #ifdef W32_NATIVE DWORD size = UNLEN + 1; TCHAR buf[UNLEN + 1]; #elif defined DJGPP #else /* UNIX */ struct passwd *pw; #endif username = getenv("USER"); if (username) { username = xstrdup(username); } else { username = getenv("LOGNAME"); if (username) { username = xstrdup(username); } else { #ifdef W32_NATIVE if (GetUserName(buf, &size)) { username = xstrdup((char *)buf); } else { /* last resort */ username = xstrdup("unknown"); } #elif defined DJGPP /* DJGPP's getlogin() checks USER, then LOGNAME, and then uses * "dosuser" as a last resort. We already checked USER and LOGNAME * and choose "unknown" as a last resort to be consistent with the * other systems. */ username = xstrdup("unknown"); #else /* UNIX */ username = getlogin(); if (username) { username = xstrdup(username); } else { pw = getpwuid(getuid()); if (pw && pw->pw_name) { username = xstrdup(pw->pw_name); } else { /* last resort */ username = xstrdup("unknown"); } } #endif } } return username; } /* * get_homedir() * * see tools.h */ char *get_homedir(void) { #ifdef W32_NATIVE char *home; BYTE homebuf[MAX_PATH + 1]; HKEY hkey; DWORD len; DWORD type; long l; if ((home = getenv("HOME"))) { home = xstrdup(home); } else { home = NULL; l = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" "Shell Folders", 0, KEY_READ, &hkey); if (l == ERROR_SUCCESS) { len = MAX_PATH; l = RegQueryValueEx(hkey, "AppData", NULL, &type, homebuf, &len); if (l == ERROR_SUCCESS && len < MAX_PATH) { RegCloseKey(hkey); home = xstrdup((char *)homebuf); } } if (!home) { home = xstrdup("C:"); } } return home; #elif defined DJGPP char *home; if ((home = getenv("HOME"))) { home = xstrdup(home); } else { home = xstrdup("C:"); } return home; #else /* UNIX */ char *home; struct passwd *pw; if ((home = getenv("HOME"))) { home = xstrdup(home); } else { pw = getpwuid(getuid()); if (pw && pw->pw_dir) { home = xstrdup(pw->pw_dir); } else { home = xstrdup(""); } } return home; #endif } /* * get_filename() * * see tools.h */ char *get_filename(const char *directory, const char *name) { char *path; size_t dirlen; dirlen = strlen(directory); path = xmalloc((dirlen + strlen(name) + 2) * sizeof(char)); strcpy(path, directory); if (dirlen == 0 || path[dirlen - 1] != PATH_SEP) { path[dirlen++] = PATH_SEP; } strcpy(path + dirlen, name); return path; } /* * expand_tilde() * * see tools.h */ char *expand_tilde(const char *filename) { char *new_filename; size_t homedirlen; if (filename[0] == '~') { new_filename = get_homedir(); homedirlen = strlen(new_filename); new_filename = xrealloc(new_filename, (homedirlen + strlen(filename)) * sizeof(char)); strcpy(new_filename + homedirlen, filename + 1); return new_filename; } else { return xstrdup(filename); } } /* * check_secure() * * see tools.h */ int check_secure(const char *pathname) { #if defined W32_NATIVE || defined DJGPP || defined __CYGWIN__ return 0; #else /* UNIX */ struct stat statbuf; if (stat(pathname, &statbuf) < 0) { return 3; } if (statbuf.st_uid != geteuid()) { return 1; } if (statbuf.st_mode & (S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)) { return 2; } return 0; #endif /* UNIX */ } /* * [DJGPP and Windows only] mkstemp_unlink() * * This function does on DOS/Windows what mkstemp() followed by unlink() do on * UNIX. * * 1. unlink() on DOS and Windows is not POSIX conformant: it does not wait * until the last file descriptor is closed before unlinking the file. * Instead, it fails (Windows) or may even mess up the file system (DOS). * 2. Windows does not have mkstemp. * 3. If a file is opened with O_TEMPORARY on Windows or DOS, it will be deleted * after the last file descriptor is closed. This is what this function does. * * Return value: file descriptor, or -1 on error (errno will be set). */ #if defined(W32_NATIVE) || defined(DJGPP) int mkstemp_unlink(char *template) { size_t templatelen; char *X; int i; int try; int ret; const char alnum[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; templatelen = strlen(template); if (templatelen < 6) { errno = EINVAL; return -1; } X = template + templatelen - 6; if (strcmp(X, "XXXXXX") != 0) { errno = EINVAL; return -1; } srand((unsigned int)time(NULL)); /* We have 62^6 possible filenames. We try 62^2=3844 times. */ ret = -1; for (try = 0; ret == -1 && try < 3844; try++) { for (i = 0; i < 6; i++) { X[i] = alnum[rand() % 36]; } #ifdef W32_NATIVE ret = _open(template, _O_CREAT | _O_EXCL | _O_RDWR | _O_TEMPORARY | _O_BINARY, _S_IREAD | _S_IWRITE); #else /* DJGPP */ ret = open(template, O_CREAT | O_EXCL | O_RDWR | O_TEMPORARY | _O_BINARY, S_IRUSR | S_IWUSR); #endif /* DJGPP */ } return ret; } #endif /* W32_NATIVE or DJGPP */ /* * tempfile() * * see tools.h */ FILE *tempfile(const char *base) { FILE *f; size_t baselen; const char *dir; size_t dirlen; char *template = NULL; size_t templatelen; int fd = -1; int saved_errno; if (!base || (*base == '\0')) { base = "tmp"; } /* the directory for the temp file */ if (!(dir = getenv("TMPDIR"))) { /* system dependent default location */ #ifdef W32_NATIVE /* there is no registry key for this (?) */ if (!(dir = getenv("TEMP"))) { if (!(dir = getenv("TMP"))) { dir = "C:"; } } #elif defined DJGPP dir = "C:"; #else /* UNIX */ #ifdef P_tmpdir dir = P_tmpdir; #else dir = "/tmp"; #endif #endif /* UNIX */ } dirlen = strlen(dir); /* the proposed file name */ baselen = strlen(base); #ifdef DJGPP /* shorten the base to two characters because of 8.3 filenames */ if (baselen > 2) { baselen = 2; } #endif /* build the template */ templatelen = dirlen + 1 + baselen + 6; template = xmalloc((templatelen + 1) * sizeof(char)); strncpy(template, dir, dirlen); if (dirlen == 0 || template[dirlen - 1] != PATH_SEP) { template[dirlen++] = PATH_SEP; } /* template is long enough */ strncpy(template + dirlen, base, baselen); strcpy(template + dirlen + baselen, "XXXXXX"); /* create the file */ #if defined(W32_NATIVE) || defined(DJGPP) if ((fd = mkstemp_unlink(template)) == -1) #else /* UNIX */ if ((fd = mkstemp(template)) == -1) #endif /* UNIX */ { goto error_exit; } /* UNIX only: set the permissions (not every mkstemp() sets them to 0600) * and unlink the file so that it gets deleted when the caller closes it */ #ifndef DJGPP #ifndef W32_NATIVE if (fchmod(fd, S_IRUSR | S_IWUSR) == -1) { goto error_exit; } if (unlink(template) != 0) { goto error_exit; } #endif /* not W32_NATIVE */ #endif /* not DJGPP */ /* get the stream from the filedescriptor */ if (!(f = fdopen(fd, "w+"))) { goto error_exit; } free(template); return f; error_exit: saved_errno = errno; if (fd >= 0) { close(fd); } if (template) { (void)remove(template); free(template); } errno = saved_errno; return NULL; } /* * lock_file() * * see tools.h */ /* Helper function that sleeps for the tenth of a second */ static void sleep_tenth_second(void) { #ifdef W32_NATIVE Sleep(100); #elif defined DJGPP usleep(100000); #else /* POSIX */ struct timespec tenth_second = { 0, 100000000 }; nanosleep(&tenth_second, NULL); #endif } int lock_file(FILE *f, int lock_type, int timeout) { int fd; int lock_success; int tenth_seconds; #ifndef W32_NATIVE struct flock lock; #endif /* not W32_NATIVE */ fd = fileno(f); #ifndef W32_NATIVE lock.l_type = (lock_type == TOOLS_LOCK_WRITE) ? F_WRLCK : F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; #endif /* not W32_NATIVE */ tenth_seconds = 0; for (;;) { errno = 0; #ifdef W32_NATIVE lock_success = (_locking(fd, _LK_NBLCK, LONG_MAX) != -1); #else /* UNIX, DJGPP */ lock_success = (fcntl(fd, F_SETLK, &lock) != -1); #endif if (lock_success || (errno != EACCES && errno != EAGAIN) || tenth_seconds / 10 >= timeout) { break; } else { sleep_tenth_second(); tenth_seconds++; } } return (lock_success ? 0 : (tenth_seconds / 10 >= timeout ? 1 : 2)); } /* * string_replace() * * see tools.h */ char *string_replace(char *str, const char *s, const char *r) { char *p, *new_str; size_t next_pos = 0; size_t slen = strlen(s); size_t rlen = strlen(r); while ((p = strstr(str + next_pos, s))) { new_str = xmalloc((strlen(str) + rlen - 1) * sizeof(char)); strncpy(new_str, str, (size_t)(p - str)); strcpy(new_str + (size_t)(p - str), r); strcpy(new_str + (size_t)(p - str) + rlen, str + (size_t)(p - str) + slen); next_pos = (size_t)(p - str) + rlen; free(str); str = new_str; } return str; }