/* * mod_site_utime, based on mod_site_misc. * Basically, just provide the SITE UTIME functionality and * nothing else. This module extends the SITE UTIME impl * in mod_site_misc by allowing for the 2 most popular * formats in use: * * SITE UTIME YYYYMMDDhhmm[ss] path * SITE UTIME path YYYYMMDDhhmm[ss] YYYYMMDDhhmm[ss] YYYYMMDDhhmm[ss] UTC * * and fixes the bug in the 1.3.1 (and older) versions that don't * allow/honor the seconds in the timestamp. Also, provide some * basic error checking and use the TZ environment work-around * found in src/support.c. * * Jim Jagielski (jim@jaguNET.com) */ /* * ProFTPD: mod_site_misc -- a module implementing miscellaneous SITE commands * * Copyright (c) 2004-2006 The ProFTPD Project * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, The ProFTPD Project team and other respective * copyright holders give permission to link this program with OpenSSL, and * distribute the resulting executable, without including the source code for * OpenSSL in the source distribution. * * $Id: mod_site_misc.c,v 1.3 2006/06/16 02:33:43 castaglia Exp $ */ #include "conf.h" #define MOD_SITE_UTIME_VERSION "mod_site_utime/1.0" static int site_utime_check_filters(cmd_rec *cmd, const char *path) { #if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP) regex_t *preg = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE); if (preg && regexec(preg, path, 0, NULL, 0) != 0) { pr_log_debug(DEBUG2, MOD_SITE_UTIME_VERSION ": 'SITE %s' denied by PathAllowFilter", cmd->arg); return -1; } preg = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE); if (preg && regexec(preg, path, 0, NULL, 0) == 0) { pr_log_debug(DEBUG2, MOD_SITE_UTIME_VERSION ": 'SITE %s' denied by PathDenyFilter", cmd->arg); return -1; } #endif return 0; } /* * Returns 1 if the timestamp is the correct length * (either 12 or 14 chars, depending on whether or not * in includes secs) and contains all digits, otherwise * returns 0. * * NOTE: Validity of the *values* (ie: sec > 60) is not * done here */ static int site_utime_isvalidtimestamp(char *timestamp) { int len; char *p = timestamp; while (*p) { if (!isdigit(*p)) { return 0; } p++; } len = p - timestamp; if (len == 12 || len == 14) { return 1; } else { return 0; } } static time_t site_utime_mktime(struct tm itm) { struct tm tm; time_t res; char *env; #ifdef HAVE_TZNAME char *tzname_dup[2]; /* The mktime(3) function has a nasty habit of changing the tzname global * variable as a side-effect. This can cause problems, as when the process * has become chrooted, and mktime(3) sets/changes tzname wrong. (For more * information on the tzname global variable, see the tzset(3) man page.) * * The best way to deal with this issue (which is especially prominent * on systems running glibc-2.3 or later, which is particularly ill-behaved * in a chrooted environment, as it assumes the ability to find system * timezone files at paths which are no longer valid within the chroot) * is to set the TZ environment variable explicitly, before starting * proftpd. You can also use the SetEnv configuration directive within * the proftpd.conf to set the TZ environment variable, e.g.: * * SetEnv TZ PST * * To try to help sites which fail to do this, the tzname global variable * will be copied prior to the mktime(3) call, and the copy restored after * the call. (Note that calling the ctime(3) and localtime(3) functions also * causes a similar overwriting/setting of the tzname environment variable.) */ memcpy(&tzname_dup, tzname, sizeof(tzname_dup)); #endif /* HAVE_TZNAME */ env = pr_env_get(session.pool, "TZ"); /* Set the TZ environment to be GMT, so that mktime(3) treats the timestamp * provided by the client as being in GMT/UTC. */ if (pr_env_set(session.pool, "TZ", "GMT") < 0) { pr_log_debug(DEBUG8, MOD_SITE_UTIME_VERSION ": error setting TZ environment variable to 'GMT': %s", strerror(errno)); } tm.tm_sec = itm.tm_sec; tm.tm_min = itm.tm_min; tm.tm_hour = itm.tm_hour; tm.tm_mday = itm.tm_mday; tm.tm_mon = (itm.tm_mon - 1); tm.tm_year = (itm.tm_year - 1900); tm.tm_wday = 0; tm.tm_yday = 0; tm.tm_isdst = -1; res = mktime(&tm); /* Restore the old TZ setting, if any. */ if (env) { if (pr_env_set(session.pool, "TZ", env) < 0) { pr_log_debug(DEBUG8, MOD_SITE_UTIME_VERSION ": error setting TZ environment variable to '%s': %s", env, strerror(errno)); } } #ifdef HAVE_TZNAME /* Restore the old tzname values prior to returning. */ memcpy(tzname, tzname_dup, sizeof(tzname_dup)); #endif /* HAVE_TZNAME */ return res; } /* * Returns 1, and a filled out time_t struct, if the * parsed timestamp is correct (eg: mon between * 1 and 12). If the timestamp results in invalid times, * then return 0. Recall that we know that the timestamp is * all digits and has strlen of 12 or 14 at this time (we * still check anyway) * * Format of timestamp: YYYYMMDDhhmm[ss] */ static int site_utime_parsetime(time_t *stamp, char *timestamp) { struct tm tm; int ret; ret = sscanf(timestamp, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (ret == 5) { tm.tm_sec = 0; } /* basic checks. Yes, mktime() should barf if these are * wrong, but pre-check. Use time.h as baseline */ if (tm.tm_year < 1900 || tm.tm_mon > 12 || tm.tm_mon == 0 || tm.tm_mday > 31 || tm.tm_mday == 0 || tm.tm_hour > 23 || tm.tm_min > 59 || (ret == 6 && tm.tm_sec > 60) || ret < 5) { return 0; } if ((*stamp = site_utime_mktime(tm)) == (time_t) -1) { return 0; /* some generic error in mktime() */ } else { return 1; } } /* Command handler */ MODRET site_utime_utime(cmd_rec *cmd) { if (cmd->argc < 2) return PR_DECLINED(cmd); if (strcasecmp(cmd->argv[1], "UTIME") == 0) { register unsigned int i; char *path = ""; struct utimbuf tmbuf; time_t actime, modtime; unsigned char *authenticated; if (cmd->argc < 4) return PR_DECLINED(cmd); authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); if (!authenticated || *authenticated == FALSE) { pr_response_add_err(R_530, "Please login with USER and PASS"); return PR_ERROR(cmd); } /* * There are 2 formats for SITE UTIME floating around out * there among clients: * * SITE UTIME YYYYMMDDhhmm path * SITE UTIME path YYYYMMDDhhmm YYYYMMDDhhmm YYYYMMDDhhmm UTC * * Up to now, we've only supported the 1st, but we now support * both. So the first thing we need to do is find out which * format we're looking at. * * Note: We support both YYYYMMDDhhmm and YYYYMMDDhhmmss */ /* * looks like format 2. If so, then check the 3 timestamps * first and look for the UTC */ if ( (cmd->argc == 7) && (strcmp(cmd->argv[6], "UTC") == 0) && (site_utime_isvalidtimestamp(cmd->argv[3])) && (site_utime_isvalidtimestamp(cmd->argv[4])) && (site_utime_isvalidtimestamp(cmd->argv[5])) ) { path = pstrcat(cmd->tmp_pool, path, cmd->argv[2], NULL); if (!site_utime_parsetime(&actime, cmd->argv[3]) || !site_utime_parsetime(&modtime, cmd->argv[4])) { pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(EINVAL)); return PR_ERROR(cmd); } } else { /* * drop down to previous impl */ if (!site_utime_isvalidtimestamp(cmd->argv[2])) { pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(EINVAL)); return PR_ERROR(cmd); } if (!site_utime_parsetime(&modtime, cmd->argv[2])) { pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(EINVAL)); return PR_ERROR(cmd); } actime = modtime; for (i = 3; i < cmd->argc; i++) path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", cmd->argv[i], NULL); } path = pr_fs_decode_path(cmd->tmp_pool, path); if (!dir_check(cmd->tmp_pool, "SITE_UTIME", G_WRITE, path, NULL)) { pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(EPERM)); return PR_ERROR(cmd); } if (site_utime_check_filters(cmd, path) < 0) { pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(EPERM)); return PR_ERROR(cmd); } tmbuf.actime = actime; tmbuf.modtime = modtime; if (utime(path, &tmbuf) < 0) { pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno)); return PR_ERROR(cmd); } pr_response_add(R_200, "SITE %s command successful", cmd->argv[1]); return PR_HANDLED(cmd); } if (strcasecmp(cmd->argv[1], "HELP") == 0) { pr_response_add(R_214, "UTIME YYYYMMDDhhmm[ss] path"); pr_response_add(R_214, "UTIME path YYYYMMDDhhmm[ss] YYYYMMDDhhmm[ss] YYYYMMDDhhmm[ss] UTC"); } return PR_DECLINED(cmd); } /* Initialization functions */ /* Module API tables */ static cmdtable site_utime_cmdtab[] = { { CMD, C_SITE, G_WRITE, site_utime_utime, FALSE, FALSE, CL_MISC }, { 0, NULL } }; module site_utime_module = { NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "site_utime", /* Module configuration handler table */ NULL, /* Module command handler table */ site_utime_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization function */ NULL, /* Session initialization function */ NULL, /* Module version */ MOD_SITE_UTIME_VERSION };