Index: head/usr.sbin/pw/psdate.c =================================================================== --- head/usr.sbin/pw/psdate.c (revision 285156) +++ head/usr.sbin/pw/psdate.c (revision 285157) @@ -1,288 +1,277 @@ /*- * Copyright (C) 1996 * David L. Nugent. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include "psdate.h" static int a2i(char const ** str) { int i = 0; char const *s = *str; if (isdigit((unsigned char)*s)) { i = atoi(s); while (isdigit((unsigned char)*s)) ++s; *str = s; } return i; } static int numerics(char const * str) { int rc = isdigit((unsigned char)*str); if (rc) while (isdigit((unsigned char)*str) || *str == 'x') ++str; return rc && !*str; } static int aindex(char const * arr[], char const ** str, int len) { int l, i; char mystr[32]; mystr[len] = '\0'; l = strlen(strncpy(mystr, *str, len)); for (i = 0; i < l; i++) mystr[i] = (char) tolower((unsigned char)mystr[i]); for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++); if (arr[i] == NULL) i = -1; else { /* Skip past it */ while (**str && isalpha((unsigned char)**str)) ++(*str); /* And any following whitespace */ while (**str && (**str == ',' || isspace((unsigned char)**str))) ++(*str); } /* Return index */ return i; } static int weekday(char const ** str) { static char const *days[] = {"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL}; return aindex(days, str, 3); } static void -parse_time(char const * str, int *hour, int *min, int *sec) +parse_datesub(char const * str, struct tm *t) { - *hour = a2i(&str); - if ((str = strchr(str, ':')) == NULL) - *min = *sec = 0; - else { - ++str; - *min = a2i(&str); - *sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str); - } -} - - -static void -parse_datesub(char const * str, int *day, int *mon, int *year) -{ struct tm tm; locale_t l; int i; char *ret; const char *valid_formats[] = { "%d-%b-%y", "%d-%b-%Y", "%d-%m-%y", "%d-%m-%Y", + "%H:%M %d-%b-%y", + "%H:%M %d-%b-%Y", + "%H:%M %d-%m-%y", + "%H:%M %d-%m-%Y", + "%H:%M:%S %d-%b-%y", + "%H:%M:%S %d-%b-%Y", + "%H:%M:%S %d-%m-%y", + "%H:%M:%S %d-%m-%Y", + "%d-%b-%y %H:%M", + "%d-%b-%Y %H:%M", + "%d-%m-%y %H:%M", + "%d-%m-%Y %H:%M", + "%d-%b-%y %H:%M:%S", + "%d-%b-%Y %H:%M:%S", + "%d-%m-%y %H:%M:%S", + "%d-%m-%Y %H:%M:%S", + "%H:%M\t%d-%b-%y", + "%H:%M\t%d-%b-%Y", + "%H:%M\t%d-%m-%y", + "%H:%M\t%d-%m-%Y", + "%H:%M\t%S %d-%b-%y", + "%H:%M\t%S %d-%b-%Y", + "%H:%M\t%S %d-%m-%y", + "%H:%M\t%S %d-%m-%Y", + "%d-%b-%y\t%H:%M", + "%d-%b-%Y\t%H:%M", + "%d-%m-%y\t%H:%M", + "%d-%m-%Y\t%H:%M", + "%d-%b-%y\t%H:%M:%S", + "%d-%b-%Y\t%H:%M:%S", + "%d-%m-%y\t%H:%M:%S", + "%d-%m-%Y\t%H:%M:%S", NULL, }; l = newlocale(LC_ALL_MASK, "C", NULL); memset(&tm, 0, sizeof(tm)); for (i=0; valid_formats[i] != NULL; i++) { ret = strptime_l(str, valid_formats[i], &tm, l); if (ret && *ret == '\0') { - *day = tm.tm_mday; - *mon = tm.tm_mon; - *year = tm.tm_year; + t->tm_mday = tm.tm_mday; + t->tm_mon = tm.tm_mon; + t->tm_year = tm.tm_year; + t->tm_hour = tm.tm_hour; + t->tm_min = tm.tm_min; + t->tm_sec = tm.tm_sec; freelocale(l); return; } } freelocale(l); errx(EXIT_FAILURE, "Invalid date"); } /*- * Parse time must be flexible, it handles the following formats: * nnnnnnnnnnn UNIX timestamp (all numeric), 0 = now * 0xnnnnnnnn UNIX timestamp in hexadecimal * 0nnnnnnnnn UNIX timestamp in octal * 0 Given time * +nnnn[smhdwoy] Given time + nnnn hours, mins, days, weeks, months or years * -nnnn[smhdwoy] Given time - nnnn hours, mins, days, weeks, months or years * dd[ ./-]mmm[ ./-]yy Date } * hh:mm:ss Time } May be combined */ time_t parse_date(time_t dt, char const * str) { char *p; int i; long val; struct tm *T; if (dt == 0) dt = time(NULL); while (*str && isspace((unsigned char)*str)) ++str; if (numerics(str)) { dt = strtol(str, &p, 0); } else if (*str == '+' || *str == '-') { val = strtol(str, &p, 0); switch (*p) { case 'h': case 'H': /* hours */ dt += (val * 3600L); break; case '\0': case 'm': case 'M': /* minutes */ dt += (val * 60L); break; case 's': case 'S': /* seconds */ dt += val; break; case 'd': case 'D': /* days */ dt += (val * 86400L); break; case 'w': case 'W': /* weeks */ dt += (val * 604800L); break; case 'o': case 'O': /* months */ T = localtime(&dt); T->tm_mon += (int) val; i = T->tm_mday; goto fixday; case 'y': case 'Y': /* years */ T = localtime(&dt); T->tm_year += (int) val; i = T->tm_mday; fixday: dt = mktime(T); T = localtime(&dt); if (T->tm_mday != i) { T->tm_mday = 1; dt = mktime(T); dt -= (time_t) 86400L; } default: /* unknown */ break; /* leave untouched */ } } else { char *q, tmp[64]; /* * Skip past any weekday prefix */ weekday(&str); strlcpy(tmp, str, sizeof(tmp)); str = tmp; T = localtime(&dt); /* * See if we can break off any timezone */ while ((q = strrchr(tmp, ' ')) != NULL) { if (strchr("(+-", q[1]) != NULL) *q = '\0'; else { int j = 1; while (q[j] && isupper((unsigned char)q[j])) ++j; if (q[j] == '\0') *q = '\0'; else break; } } - /* - * See if there is a time hh:mm[:ss] - */ - if ((p = strchr(tmp, ':')) == NULL) { - - /* - * No time string involved - */ - T->tm_hour = T->tm_min = T->tm_sec = 0; - parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year); - } else { - char datestr[64], timestr[64]; - - /* - * Let's chip off the time string - */ - if ((q = strpbrk(p, " \t")) != NULL) { /* Time first? */ - int l = q - str; - - strlcpy(timestr, str, l + 1); - strlcpy(datestr, q + 1, sizeof(datestr)); - parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec); - parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year); - } else if ((q = strrchr(tmp, ' ')) != NULL) { /* Time last */ - int l = q - tmp; - - strlcpy(timestr, q + 1, sizeof(timestr)); - strlcpy(datestr, tmp, l + 1); - } else /* Bail out */ - return dt; - parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec); - parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year); - } + parse_datesub(tmp, T); dt = mktime(T); } return dt; } Index: head/usr.sbin/pw/tests/pw_useradd.sh =================================================================== --- head/usr.sbin/pw/tests/pw_useradd.sh (revision 285156) +++ head/usr.sbin/pw/tests/pw_useradd.sh (revision 285157) @@ -1,222 +1,228 @@ # $FreeBSD$ # Import helper functions . $(atf_get_srcdir)/helper_functions.shin # Test add user atf_test_case user_add user_add_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test atf_check -s exit:0 -o match:"^test:.*" \ grep "^test:.*" $HOME/master.passwd } # Test add user with option -N atf_test_case user_add_noupdate user_add_noupdate_body() { populate_etc_skel atf_check -s exit:0 -o match:"^test:.*" ${PW} useradd test -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with comments atf_test_case user_add_comments user_add_comments_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -c "Test User,work,123,456" atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ grep "^test:.*:Test User,work,123,456:" $HOME/master.passwd } # Test add user with comments and option -N atf_test_case user_add_comments_noupdate user_add_comments_noupdate_body() { populate_etc_skel atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \ ${PW} useradd test -c "Test User,work,123,456" -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with invalid comments atf_test_case user_add_comments_invalid user_add_comments_invalid_body() { populate_etc_skel atf_check -s exit:65 -e match:"invalid character" \ ${PW} useradd test -c "Test User,work,123:456,456" atf_check -s exit:1 -o empty \ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd } # Test add user with invalid comments and option -N atf_test_case user_add_comments_invalid_noupdate user_add_comments_invalid_noupdate_body() { populate_etc_skel atf_check -s exit:65 -e match:"invalid character" \ ${PW} useradd test -c "Test User,work,123:456,456" -N atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd } # Test add user with alternate homedir atf_test_case user_add_homedir user_add_homedir_body() { populate_etc_skel atf_check -s exit:0 ${PW} useradd test -d /foo/bar atf_check -s exit:0 -o match:"^test:\*:.*::0:0:User &:/foo/bar:.*" \ ${PW} usershow test } # Test add user with account expiration as an epoch date atf_test_case user_add_account_expiration_epoch user_add_account_expiration_epoch_body() { populate_etc_skel DATE=`date -j -v+1d "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${DATE}:.*" \ ${PW} usershow test } # Test add user with account expiration as a DD-MM-YYYY date atf_test_case user_add_account_expiration_date_numeric user_add_account_expiration_date_numeric_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%m-%Y"` EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \ ${PW} usershow test } # Test add user with account expiration as a DD-MM-YYYY date atf_test_case user_add_account_expiration_date_month user_add_account_expiration_date_month_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%b-%Y"` EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -e ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \ ${PW} usershow test } # Test add user with account expiration as a relative date atf_test_case user_add_account_expiration_date_relative user_add_account_expiration_date_relative_body() { populate_etc_skel EPOCH=`date -j -v+13m "+%s"` BUF=`expr $EPOCH + 5` atf_check -s exit:0 ${PW} useradd test -e +13o TIME=`${PW} usershow test | awk -F ':' '{print $7}'` [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds." } # Test add user with password expiration as an epoch date atf_test_case user_add_password_expiration_epoch user_add_password_expiration_epoch_body() { populate_etc_skel DATE=`date -j -v+1d "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${DATE}:0:.*" \ ${PW} usershow test } # Test add user with password expiration as a DD-MM-YYYY date atf_test_case user_add_password_expiration_date_numeric user_add_password_expiration_date_numeric_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%m-%Y"` EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \ ${PW} usershow test } # Test add user with password expiration as a DD-MMM-YYYY date atf_test_case user_add_password_expiration_date_month user_add_password_expiration_date_month_body() { populate_etc_skel DATE=`date -j -v+1d "+%d-%b-%Y"` EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"` atf_check -s exit:0 ${PW} useradd test -p ${DATE} atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \ ${PW} usershow test } # Test add user with password expiration as a relative date atf_test_case user_add_password_expiration_date_relative user_add_password_expiration_date_relative_body() { populate_etc_skel EPOCH=`date -j -v+13m "+%s"` BUF=`expr $EPOCH + 5` atf_check -s exit:0 ${PW} useradd test -p +13o TIME=`${PW} usershow test | awk -F ':' '{print $6}'` [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds." } atf_test_case user_add_name_too_long user_add_name_too_long_body() { populate_etc_skel atf_check -e match:"too long" -s exit:64 \ ${PW} useradd name_very_vert_very_very_very_long } atf_test_case user_add_expiration user_add_expiration_body() { populate_etc_skel atf_check -s exit:0 \ ${PW} useradd foo -e 20-03-2043 atf_check -o inline:"foo:*:1001:1001::0:2310422400:User &:/home/foo:/bin/sh\n" \ -s exit:0 grep "^foo" ${HOME}/master.passwd atf_check -s exit:0 ${PW} userdel foo atf_check -s exit:0 \ ${PW} useradd foo -e 20-03-43 atf_check -o inline:"foo:*:1001:1001::0:2310422400:User &:/home/foo:/bin/sh\n" \ -s exit:0 grep "^foo" ${HOME}/master.passwd atf_check -s exit:0 ${PW} userdel foo atf_check -s exit:0 \ ${PW} useradd foo -e 20-Mar-2043 atf_check -o inline:"foo:*:1001:1001::0:2310422400:User &:/home/foo:/bin/sh\n" \ -s exit:0 grep "^foo" ${HOME}/master.passwd atf_check -s exit:0 ${PW} userdel foo atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ ${PW} useradd foo -e 20-Foo-2043 atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ ${PW} useradd foo -e 20-13-2043 + atf_check -s exit:0 ${PW} useradd foo -e "12:00 20-03-2043" + atf_check -s exit:0 ${PW} userdel foo + atf_check -e inline:"pw: Invalid date\n" -s exit:1 \ + ${PW} useradd foo -e "12 20-03-2043" + atf_check -s exit:0 ${PW} useradd foo -e "20-03-2043 12:00" + atf_check -s exit:0 ${PW} userdel foo } atf_init_test_cases() { atf_add_test_case user_add atf_add_test_case user_add_noupdate atf_add_test_case user_add_comments atf_add_test_case user_add_comments_noupdate atf_add_test_case user_add_comments_invalid atf_add_test_case user_add_comments_invalid_noupdate atf_add_test_case user_add_homedir atf_add_test_case user_add_account_expiration_epoch atf_add_test_case user_add_account_expiration_date_numeric atf_add_test_case user_add_account_expiration_date_month atf_add_test_case user_add_account_expiration_date_relative atf_add_test_case user_add_password_expiration_epoch atf_add_test_case user_add_password_expiration_date_numeric atf_add_test_case user_add_password_expiration_date_month atf_add_test_case user_add_password_expiration_date_relative atf_add_test_case user_add_name_too_long atf_add_test_case user_add_expiration }