| File: | src/usr.sbin/cron/do_command.c |
| Warning: | line 470, column 15 The left operand of '>' is a garbage value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* $OpenBSD: do_command.c,v 1.61 2020/04/16 17:51:56 millert Exp $ */ | |||
| 2 | ||||
| 3 | /* Copyright 1988,1990,1993,1994 by Paul Vixie | |||
| 4 | * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") | |||
| 5 | * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. | |||
| 6 | * Copyright (c) 2018 Job Snijders <job@openbsd.org> | |||
| 7 | * | |||
| 8 | * Permission to use, copy, modify, and distribute this software for any | |||
| 9 | * purpose with or without fee is hereby granted, provided that the above | |||
| 10 | * copyright notice and this permission notice appear in all copies. | |||
| 11 | * | |||
| 12 | * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES | |||
| 13 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| 14 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR | |||
| 15 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| 16 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| 17 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT | |||
| 18 | * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| 19 | */ | |||
| 20 | ||||
| 21 | #include <sys/types.h> | |||
| 22 | #include <sys/wait.h> | |||
| 23 | ||||
| 24 | #include <bitstring.h> /* for structs.h */ | |||
| 25 | #include <bsd_auth.h> | |||
| 26 | #include <ctype.h> | |||
| 27 | #include <err.h> | |||
| 28 | #include <errno(*__errno()).h> | |||
| 29 | #include <fcntl.h> | |||
| 30 | #include <limits.h> | |||
| 31 | #include <login_cap.h> | |||
| 32 | #include <pwd.h> | |||
| 33 | #include <signal.h> | |||
| 34 | #include <stdio.h> | |||
| 35 | #include <stdlib.h> | |||
| 36 | #include <string.h> | |||
| 37 | #include <syslog.h> | |||
| 38 | #include <time.h> /* for structs.h */ | |||
| 39 | #include <unistd.h> | |||
| 40 | #include <vis.h> | |||
| 41 | ||||
| 42 | #include "config.h" | |||
| 43 | #include "pathnames.h" | |||
| 44 | #include "macros.h" | |||
| 45 | #include "structs.h" | |||
| 46 | #include "funcs.h" | |||
| 47 | ||||
| 48 | static void child_process(entry *, user *); | |||
| 49 | ||||
| 50 | pid_t | |||
| 51 | do_command(entry *e, user *u) | |||
| 52 | { | |||
| 53 | pid_t pid; | |||
| 54 | ||||
| 55 | /* fork to become asynchronous -- parent process is done immediately, | |||
| 56 | * and continues to run the normal cron code, which means return to | |||
| 57 | * tick(). the child and grandchild don't leave this function, alive. | |||
| 58 | * | |||
| 59 | * vfork() is unsuitable, since we have much to do, and the parent | |||
| 60 | * needs to be able to run off and fork other processes. | |||
| 61 | */ | |||
| 62 | switch ((pid = fork())) { | |||
| 63 | case -1: | |||
| 64 | syslog(LOG_ERR3, "(CRON) CAN'T FORK (%m)"); | |||
| 65 | break; | |||
| 66 | case 0: | |||
| 67 | /* child process */ | |||
| 68 | child_process(e, u); | |||
| 69 | _exit(EXIT_SUCCESS0); | |||
| 70 | break; | |||
| 71 | default: | |||
| 72 | /* parent process */ | |||
| 73 | if ((e->flags & SINGLE_JOB0x80) == 0) | |||
| 74 | pid = -1; | |||
| 75 | break; | |||
| 76 | } | |||
| 77 | ||||
| 78 | /* only return pid if a singleton */ | |||
| 79 | return (pid); | |||
| 80 | } | |||
| 81 | ||||
| 82 | static void | |||
| 83 | child_process(entry *e, user *u) | |||
| 84 | { | |||
| 85 | FILE *in; | |||
| 86 | int stdin_pipe[2], stdout_pipe[2]; | |||
| 87 | char **p, *input_data, *usernm; | |||
| 88 | auth_session_t *as; | |||
| 89 | login_cap_t *lc; | |||
| 90 | extern char **environ; | |||
| 91 | ||||
| 92 | /* mark ourselves as different to PS command watchers */ | |||
| 93 | setproctitle("running job"); | |||
| 94 | ||||
| 95 | /* close sockets from parent (i.e. cronSock) */ | |||
| 96 | closefrom(3); | |||
| 97 | ||||
| 98 | /* discover some useful and important environment settings | |||
| 99 | */ | |||
| 100 | usernm = e->pwd->pw_name; | |||
| 101 | ||||
| 102 | /* our parent is watching for our death by catching SIGCHLD. we | |||
| 103 | * do not care to watch for our children's deaths this way -- we | |||
| 104 | * use wait() explicitly. so we have to reset the signal (which | |||
| 105 | * was inherited from the parent). | |||
| 106 | */ | |||
| 107 | (void) signal(SIGCHLD20, SIG_DFL(void (*)(int))0); | |||
| 108 | ||||
| 109 | /* create some pipes to talk to our future child | |||
| 110 | */ | |||
| 111 | if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { | |||
| ||||
| 112 | syslog(LOG_ERR3, "(CRON) PIPE (%m)"); | |||
| 113 | _exit(EXIT_FAILURE1); | |||
| 114 | } | |||
| 115 | ||||
| 116 | /* since we are a forked process, we can diddle the command string | |||
| 117 | * we were passed -- nobody else is going to use it again, right? | |||
| 118 | * | |||
| 119 | * if a % is present in the command, previous characters are the | |||
| 120 | * command, and subsequent characters are the additional input to | |||
| 121 | * the command. An escaped % will have the escape character stripped | |||
| 122 | * from it. Subsequent %'s will be transformed into newlines, | |||
| 123 | * but that happens later. | |||
| 124 | */ | |||
| 125 | /*local*/{ | |||
| 126 | int escaped = FALSE0; | |||
| 127 | int ch; | |||
| 128 | char *p; | |||
| 129 | ||||
| 130 | for (input_data = p = e->cmd; | |||
| 131 | (ch = *input_data) != '\0'; | |||
| 132 | input_data++, p++) { | |||
| 133 | if (p != input_data) | |||
| 134 | *p = ch; | |||
| 135 | if (escaped) { | |||
| 136 | if (ch == '%') | |||
| 137 | *--p = ch; | |||
| 138 | escaped = FALSE0; | |||
| 139 | continue; | |||
| 140 | } | |||
| 141 | if (ch == '\\') { | |||
| 142 | escaped = TRUE1; | |||
| 143 | continue; | |||
| 144 | } | |||
| 145 | if (ch == '%') { | |||
| 146 | *input_data++ = '\0'; | |||
| 147 | break; | |||
| 148 | } | |||
| 149 | } | |||
| 150 | *p = '\0'; | |||
| 151 | } | |||
| 152 | ||||
| 153 | /* fork again, this time so we can exec the user's command. | |||
| 154 | */ | |||
| 155 | ||||
| 156 | pid_t jobpid; | |||
| 157 | switch (jobpid = fork()) { | |||
| 158 | case -1: | |||
| 159 | syslog(LOG_ERR3, "(CRON) CAN'T FORK (%m)"); | |||
| 160 | _exit(EXIT_FAILURE1); | |||
| 161 | /*NOTREACHED*/ | |||
| 162 | case 0: | |||
| 163 | /* write a log message. we've waited this long to do it | |||
| 164 | * because it was not until now that we knew the PID that | |||
| 165 | * the actual user command shell was going to get and the | |||
| 166 | * PID is part of the log message. | |||
| 167 | */ | |||
| 168 | if ((e->flags & DONT_LOG0x20) == 0) { | |||
| 169 | char *x; | |||
| 170 | if (stravis(&x, e->cmd, 0) != -1) { | |||
| 171 | syslog(LOG_INFO6, "(%s) CMD (%s)", usernm, x); | |||
| 172 | free(x); | |||
| 173 | } | |||
| 174 | } | |||
| 175 | ||||
| 176 | /* get new pgrp, void tty, etc. | |||
| 177 | */ | |||
| 178 | (void) setsid(); | |||
| 179 | ||||
| 180 | /* close the pipe ends that we won't use. this doesn't affect | |||
| 181 | * the parent, who has to read and write them; it keeps the | |||
| 182 | * kernel from recording us as a potential client TWICE -- | |||
| 183 | * which would keep it from sending SIGPIPE in otherwise | |||
| 184 | * appropriate circumstances. | |||
| 185 | */ | |||
| 186 | close(stdin_pipe[WRITE_PIPE1]); | |||
| 187 | close(stdout_pipe[READ_PIPE0]); | |||
| 188 | ||||
| 189 | /* grandchild process. make std{in,out} be the ends of | |||
| 190 | * pipes opened by our daddy; make stderr go to stdout. | |||
| 191 | */ | |||
| 192 | if (stdin_pipe[READ_PIPE0] != STDIN_FILENO0) { | |||
| 193 | dup2(stdin_pipe[READ_PIPE0], STDIN_FILENO0); | |||
| 194 | close(stdin_pipe[READ_PIPE0]); | |||
| 195 | } | |||
| 196 | if (stdout_pipe[WRITE_PIPE1] != STDOUT_FILENO1) { | |||
| 197 | dup2(stdout_pipe[WRITE_PIPE1], STDOUT_FILENO1); | |||
| 198 | close(stdout_pipe[WRITE_PIPE1]); | |||
| 199 | } | |||
| 200 | dup2(STDOUT_FILENO1, STDERR_FILENO2); | |||
| 201 | ||||
| 202 | /* | |||
| 203 | * From this point on, anything written to stderr will be | |||
| 204 | * mailed to the user as output. | |||
| 205 | */ | |||
| 206 | ||||
| 207 | /* XXX - should just pass in a login_cap_t * */ | |||
| 208 | if ((lc = login_getclass(e->pwd->pw_class)) == NULL((void *)0)) { | |||
| 209 | warnx("unable to get login class for %s", | |||
| 210 | e->pwd->pw_name); | |||
| 211 | syslog(LOG_ERR3, "(CRON) CAN'T GET LOGIN CLASS (%s)", | |||
| 212 | e->pwd->pw_name); | |||
| 213 | _exit(EXIT_FAILURE1); | |||
| 214 | } | |||
| 215 | if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL0x00ff) == -1) { | |||
| 216 | warn("setusercontext failed for %s", e->pwd->pw_name); | |||
| 217 | syslog(LOG_ERR3, "(%s) SETUSERCONTEXT FAILED (%m)", | |||
| 218 | e->pwd->pw_name); | |||
| 219 | _exit(EXIT_FAILURE1); | |||
| 220 | } | |||
| 221 | as = auth_open(); | |||
| 222 | if (as == NULL((void *)0) || auth_setpwd(as, e->pwd) != 0) { | |||
| 223 | warn("auth_setpwd"); | |||
| 224 | syslog(LOG_ERR3, "(%s) AUTH_SETPWD FAILED (%m)", | |||
| 225 | e->pwd->pw_name); | |||
| 226 | _exit(EXIT_FAILURE1); | |||
| 227 | } | |||
| 228 | if (auth_approval(as, lc, usernm, "cron") <= 0) { | |||
| 229 | warnx("approval failed for %s", e->pwd->pw_name); | |||
| 230 | syslog(LOG_ERR3, "(%s) APPROVAL FAILED (cron)", | |||
| 231 | e->pwd->pw_name); | |||
| 232 | _exit(EXIT_FAILURE1); | |||
| 233 | } | |||
| 234 | auth_close(as); | |||
| 235 | login_close(lc); | |||
| 236 | ||||
| 237 | /* If no PATH specified in crontab file but | |||
| 238 | * we just added one via login.conf, add it to | |||
| 239 | * the crontab environment. | |||
| 240 | */ | |||
| 241 | if (env_get("PATH", e->envp) == NULL((void *)0) && environ != NULL((void *)0)) { | |||
| 242 | for (p = environ; *p; p++) { | |||
| 243 | if (strncmp(*p, "PATH=", 5) == 0) { | |||
| 244 | e->envp = env_set(e->envp, *p); | |||
| 245 | break; | |||
| 246 | } | |||
| 247 | } | |||
| 248 | } | |||
| 249 | chdir(env_get("HOME", e->envp)); | |||
| 250 | ||||
| 251 | (void) signal(SIGPIPE13, SIG_DFL(void (*)(int))0); | |||
| 252 | ||||
| 253 | /* | |||
| 254 | * Exec the command. | |||
| 255 | */ | |||
| 256 | { | |||
| 257 | char *shell = env_get("SHELL", e->envp); | |||
| 258 | ||||
| 259 | execle(shell, shell, "-c", e->cmd, (char *)NULL((void *)0), e->envp); | |||
| 260 | warn("unable to execute %s", shell); | |||
| 261 | syslog(LOG_ERR3, "(%s) CAN'T EXEC (%s: %m)", | |||
| 262 | e->pwd->pw_name, shell); | |||
| 263 | _exit(EXIT_FAILURE1); | |||
| 264 | } | |||
| 265 | break; | |||
| 266 | default: | |||
| 267 | /* parent process */ | |||
| 268 | break; | |||
| 269 | } | |||
| 270 | ||||
| 271 | /* middle process, child of original cron, parent of process running | |||
| 272 | * the user's command. | |||
| 273 | */ | |||
| 274 | ||||
| 275 | /* close the ends of the pipe that will only be referenced in the | |||
| 276 | * grandchild process... | |||
| 277 | */ | |||
| 278 | close(stdin_pipe[READ_PIPE0]); | |||
| 279 | close(stdout_pipe[WRITE_PIPE1]); | |||
| 280 | ||||
| 281 | /* | |||
| 282 | * write, to the pipe connected to child's stdin, any input specified | |||
| 283 | * after a % in the crontab entry. while we copy, convert any | |||
| 284 | * additional %'s to newlines. when done, if some characters were | |||
| 285 | * written and the last one wasn't a newline, write a newline. | |||
| 286 | * | |||
| 287 | * Note that if the input data won't fit into one pipe buffer (2K | |||
| 288 | * or 4K on most BSD systems), and the child doesn't read its stdin, | |||
| 289 | * we would block here. thus we must fork again. | |||
| 290 | */ | |||
| 291 | ||||
| 292 | pid_t stdinjob; | |||
| 293 | if (*input_data && (stdinjob = fork()) == 0) { | |||
| 294 | FILE *out = fdopen(stdin_pipe[WRITE_PIPE1], "w"); | |||
| 295 | int need_newline = FALSE0; | |||
| 296 | int escaped = FALSE0; | |||
| 297 | int ch; | |||
| 298 | ||||
| 299 | /* close the pipe we don't use, since we inherited it and | |||
| 300 | * are part of its reference count now. | |||
| 301 | */ | |||
| 302 | close(stdout_pipe[READ_PIPE0]); | |||
| 303 | ||||
| 304 | /* translation: | |||
| 305 | * \% -> % | |||
| 306 | * % -> \n | |||
| 307 | * \x -> \x for all x != % | |||
| 308 | */ | |||
| 309 | while ((ch = *input_data++) != '\0') { | |||
| 310 | if (escaped) { | |||
| 311 | if (ch != '%') | |||
| 312 | putc('\\', out)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out)); | |||
| 313 | } else { | |||
| 314 | if (ch == '%') | |||
| 315 | ch = '\n'; | |||
| 316 | } | |||
| 317 | ||||
| 318 | if (!(escaped = (ch == '\\'))) { | |||
| 319 | putc(ch, out)(!__isthreaded ? __sputc(ch, out) : (putc)(ch, out)); | |||
| 320 | need_newline = (ch != '\n'); | |||
| 321 | } | |||
| 322 | } | |||
| 323 | if (escaped) | |||
| 324 | putc('\\', out)(!__isthreaded ? __sputc('\\', out) : (putc)('\\', out)); | |||
| 325 | if (need_newline) | |||
| 326 | putc('\n', out)(!__isthreaded ? __sputc('\n', out) : (putc)('\n', out)); | |||
| 327 | ||||
| 328 | /* close the pipe, causing an EOF condition. fclose causes | |||
| 329 | * stdin_pipe[WRITE_PIPE] to be closed, too. | |||
| 330 | */ | |||
| 331 | fclose(out); | |||
| 332 | ||||
| 333 | _exit(EXIT_SUCCESS0); | |||
| 334 | } | |||
| 335 | ||||
| 336 | /* close the pipe to the grandkiddie's stdin, since its wicked uncle | |||
| 337 | * ernie back there has it open and will close it when he's done. | |||
| 338 | */ | |||
| 339 | close(stdin_pipe[WRITE_PIPE1]); | |||
| 340 | ||||
| 341 | /* | |||
| 342 | * read output from the grandchild. it's stderr has been redirected to | |||
| 343 | * it's stdout, which has been redirected to our pipe. if there is any | |||
| 344 | * output, we'll be mailing it to the user whose crontab this is... | |||
| 345 | * when the grandchild exits, we'll get EOF. | |||
| 346 | */ | |||
| 347 | ||||
| 348 | (void) signal(SIGPIPE13, SIG_IGN(void (*)(int))1); | |||
| 349 | in = fdopen(stdout_pipe[READ_PIPE0], "r"); | |||
| 350 | ||||
| 351 | char *mailto; | |||
| 352 | FILE *mail = NULL((void *)0); | |||
| 353 | int status = 0; | |||
| 354 | pid_t mailpid; | |||
| 355 | size_t bytes = 1; | |||
| 356 | ||||
| 357 | if (in != NULL((void *)0)) { | |||
| 358 | int ch = getc(in)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)( *(in)->_p++)) : (getc)(in)); | |||
| 359 | ||||
| 360 | if (ch != EOF(-1)) { | |||
| 361 | ||||
| 362 | /* get name of recipient. this is MAILTO if set to a | |||
| 363 | * valid local username; USER otherwise. | |||
| 364 | */ | |||
| 365 | mailto = env_get("MAILTO", e->envp); | |||
| 366 | if (!mailto) { | |||
| 367 | /* MAILTO not present, set to USER. | |||
| 368 | */ | |||
| 369 | mailto = usernm; | |||
| 370 | } else if (!*mailto || !safe_p(usernm, mailto)) { | |||
| 371 | mailto = NULL((void *)0); | |||
| 372 | } | |||
| 373 | ||||
| 374 | /* if we are supposed to be mailing, MAILTO will | |||
| 375 | * be non-NULL. only in this case should we set | |||
| 376 | * up the mail command and subjects and stuff... | |||
| 377 | */ | |||
| 378 | ||||
| 379 | if (mailto) { | |||
| 380 | char **env; | |||
| 381 | char mailcmd[MAX_COMMAND1000]; | |||
| 382 | char hostname[HOST_NAME_MAX255 + 1]; | |||
| 383 | ||||
| 384 | gethostname(hostname, sizeof(hostname)); | |||
| 385 | if (snprintf(mailcmd, sizeof mailcmd, MAILFMT"%s -FCronDaemon -odi -oem -oi -t", | |||
| 386 | MAILARG"/usr/sbin/sendmail") >= sizeof mailcmd) { | |||
| 387 | syslog(LOG_ERR3, | |||
| 388 | "(%s) ERROR (mailcmd too long)", | |||
| 389 | e->pwd->pw_name); | |||
| 390 | (void) _exit(EXIT_FAILURE1); | |||
| 391 | } | |||
| 392 | if (!(mail = cron_popen(mailcmd, "w", e->pwd, | |||
| 393 | &mailpid))) { | |||
| 394 | syslog(LOG_ERR3, "(%s) POPEN (%s)", | |||
| 395 | e->pwd->pw_name, mailcmd); | |||
| 396 | (void) _exit(EXIT_FAILURE1); | |||
| 397 | } | |||
| 398 | fprintf(mail, "From: root (Cron Daemon)\n"); | |||
| 399 | fprintf(mail, "To: %s\n", mailto); | |||
| 400 | fprintf(mail, "Subject: Cron <%s@%s> %s\n", | |||
| 401 | usernm, first_word(hostname, "."), | |||
| 402 | e->cmd); | |||
| 403 | fprintf(mail, "Auto-Submitted: auto-generated\n"); | |||
| 404 | for (env = e->envp; *env; env++) | |||
| 405 | fprintf(mail, "X-Cron-Env: <%s>\n", | |||
| 406 | *env); | |||
| 407 | fprintf(mail, "\n"); | |||
| 408 | ||||
| 409 | /* this was the first char from the pipe | |||
| 410 | */ | |||
| 411 | fputc(ch, mail); | |||
| 412 | } | |||
| 413 | ||||
| 414 | /* we have to read the input pipe no matter whether | |||
| 415 | * we mail or not, but obviously we only write to | |||
| 416 | * mail pipe if we ARE mailing. | |||
| 417 | */ | |||
| 418 | ||||
| 419 | while (EOF(-1) != (ch = getc(in)(!__isthreaded ? (--(in)->_r < 0 ? __srget(in) : (int)( *(in)->_p++)) : (getc)(in)))) { | |||
| 420 | bytes++; | |||
| 421 | if (mail) | |||
| 422 | fputc(ch, mail); | |||
| 423 | } | |||
| 424 | ||||
| 425 | } /*if data from grandchild*/ | |||
| 426 | ||||
| 427 | fclose(in); /* also closes stdout_pipe[READ_PIPE] */ | |||
| 428 | } | |||
| 429 | ||||
| 430 | /* wait for children to die. | |||
| 431 | */ | |||
| 432 | int waiter; | |||
| 433 | if (jobpid > 0) { | |||
| 434 | while (waitpid(jobpid, &waiter, 0) == -1 && errno(*__errno()) == EINTR4) | |||
| 435 | ; | |||
| 436 | ||||
| 437 | /* If everything went well, and -n was set, _and_ we have mail, | |||
| 438 | * we won't be mailing... so shoot the messenger! | |||
| 439 | */ | |||
| 440 | if (WIFEXITED(waiter)(((waiter) & 0177) == 0) && WEXITSTATUS(waiter)(int)(((unsigned)(waiter) >> 8) & 0xff) == 0 | |||
| 441 | && (e->flags & MAIL_WHEN_ERR0x40) == MAIL_WHEN_ERR0x40 | |||
| 442 | && mail) { | |||
| 443 | kill(mailpid, SIGKILL9); | |||
| 444 | (void)fclose(mail); | |||
| 445 | mail = NULL((void *)0); | |||
| 446 | } | |||
| 447 | ||||
| 448 | /* only close pipe if we opened it -- i.e., we're mailing... */ | |||
| 449 | if (mail) { | |||
| 450 | /* | |||
| 451 | * Note: the pclose will probably see the termination | |||
| 452 | * of the grandchild in addition to the mail process, | |||
| 453 | * since it (the grandchild) is likely to exit after | |||
| 454 | * closing its stdout. | |||
| 455 | */ | |||
| 456 | status = cron_pclose(mail, mailpid); | |||
| 457 | } | |||
| 458 | ||||
| 459 | /* if there was output and we could not mail it, | |||
| 460 | * log the facts so the poor user can figure out | |||
| 461 | * what's going on. | |||
| 462 | */ | |||
| 463 | if (mail && status) { | |||
| 464 | syslog(LOG_NOTICE5, "(%s) MAIL (mailed %zu byte" | |||
| 465 | "%s of output but got status 0x%04x)", usernm, | |||
| 466 | bytes, (bytes == 1) ? "" : "s", status); | |||
| 467 | } | |||
| 468 | } | |||
| 469 | ||||
| 470 | if (stdinjob > 0) | |||
| ||||
| 471 | while (waitpid(stdinjob, &waiter, 0) == -1 && errno(*__errno()) == EINTR4) | |||
| 472 | ; | |||
| 473 | } | |||
| 474 | ||||
| 475 | int | |||
| 476 | safe_p(const char *usernm, const char *s) | |||
| 477 | { | |||
| 478 | static const char safe_delim[] = "@!:%+-.,"; /* conservative! */ | |||
| 479 | const char *t; | |||
| 480 | int ch, first; | |||
| 481 | ||||
| 482 | for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) { | |||
| 483 | if (isascii(ch) && isprint(ch) && | |||
| 484 | (isalnum(ch) || ch == '_' || | |||
| 485 | (!first && strchr(safe_delim, ch)))) | |||
| 486 | continue; | |||
| 487 | syslog(LOG_WARNING4, "(%s) UNSAFE (%s)", usernm, s); | |||
| 488 | return (FALSE0); | |||
| 489 | } | |||
| 490 | return (TRUE1); | |||
| 491 | } |