Re: man 8 usermod -e, --expiredate EXPIRE_DATE information incorrect (can't be blank, must be -1 to clear)
From: Alejandro Colomar (man-pages) <hidden>
Date: 2021-12-14 10:55:56
Hello David, On 12/10/21 23:54, David C. Rankin wrote:
Dear usermod man page maintainers, There is an error in the description of the option for: -e, --expiredate EXPIRE_DATE The man page states: An empty EXPIRE_DATE argument will disable the expiration of the account. This is incorrect. The EXPIRE_DATE argument CANNOT be empty or it will take the LOGIN as the value for the EXPIRE_DATE. Instead, the correct use is "-e -1" or "--expiredate -1" where "-1" (-one) is provided as the EXPIRE_DATE value. See, e.g.: https://stackoverflow.com/a/30769911/3422102
Thanks for the report. I tried a few combinations that allow to pass an empty argument to the expiredate option. From a terminal, I expire the password (and check it) with: $ sudo usermod -e 1 alx $ su alx Password: Your account has expired; please contact your system administrator. su: Authentication failure $ Then from another terminal (logged in as root), I re-enable it, with either of the following: # usermod -e '' alx # usermod --expiredate= alx And then I can log in correctly: $ su alx Password: $ I commented on the stackoverflow post with this info. Cheers, Alex See some more details below: =========== https://github.com/shadow-maint/shadow/tree/master is the source code for the passwd package available in Debian. Parsing the options and arguments in <src/usermod.c>: case 'e': if ('\0' != *optarg) { user_newexpire = strtoday (optarg); if (user_newexpire < -1) { fprintf (stderr, _("%s: invalid date '%s'\n"), Prog, optarg); exit (E_BAD_ARG); } user_newexpire *= DAY / SCALE; } else { user_newexpire = -1; } eflg = true; break; So both '' and '-1' are treated in the same way, but '-1' is undocumented. I'm wondering what other undocumented formats are accepted, since I succeeded with a positive number, which is also undocumented, so let's see strtoday(): /* * strtoday() now uses get_date() (borrowed from GNU shellutils) * which can handle many date formats, for example: * 1970-09-17 # ISO 8601. * 70-9-17 # This century assumed by default. * 70-09-17 # Leading zeros are ignored. * 9/17/72 # Common U.S. writing. * 24 September 1972 * 24 Sept 72 # September has a special abbreviation. * 24 Sep 72 # Three-letter abbreviations always allowed. * Sep 24, 1972 * 24-sep-72 * 24sep72 */ long strtoday (const char *str) { time_t t; bool isnum = true; const char *s = str; /* * get_date() interprets an empty string as the current date, * which is not what we expect, unless you're a BOFH :-). * (useradd sets sp_expire = current date for new lusers) */ if ((NULL == str) || ('\0' == *str)) { return -1; } /* If a numerical value is provided, this is already a number of * days since EPOCH. */ if ('-' == *s) { s++; } while (' ' == *s) { s++; } while (isnum && ('\0' != *s)) { if (!isdigit (*s)) { isnum = false; } s++; } if (isnum) { long retdate; if (getlong (str, &retdate) == 0) { return -2; } return retdate; } t = get_date (str, NULL); if ((time_t) - 1 == t) { return -2; } /* convert seconds to days since 1970-01-01 */ return (long) (t + DAY / 2) / DAY; } So a simple number is "day since the Epoch". Other accepted formats are in the comment above the function. BTW, since this function understands "" as -1, the parsing in <src/usermod.c> could be simplified (remove the special case for ""), so I'll send a patch for that. static void date_to_str (/*@unique@*//*@out@*/char *buf, size_t maxsize, long int date) { struct tm *tp; if (date < 0) { strncpy (buf, "never", maxsize); } else { time_t t = (time_t) date; tp = gmtime (&t); #ifdef HAVE_STRFTIME strftime (buf, maxsize, "%Y-%m-%d", tp); #else (void) snprintf (buf, maxsize, "%04d-%02d-%02d", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday); #endif /* HAVE_STRFTIME */ } buf[maxsize - 1] = '\0'; } Then, the number is converted back to string, and '-1' means 'never', as you said. -- Alejandro Colomar Linux man-pages comaintainer; http://www.kernel.org/doc/man-pages/