* `LICENSE' that comes with the fcron source distribution.
*/
- /* $Id: fcrondyn.c,v 1.1 2002-02-25 18:38:42 thib Exp $ */
+ /* $Id: fcrondyn.c,v 1.2 2002-03-02 17:23:55 thib Exp $ */
/* fcrondyn : interact dynamically with running fcron process :
* - list jobs, with their status, next time of execution, etc
#include "fcrondyn.h"
#include "allow.h"
+#include "read_string.h"
-char rcs_info[] = "$Id: fcrondyn.c,v 1.1 2002-02-25 18:38:42 thib Exp $";
+char rcs_info[] = "$Id: fcrondyn.c,v 1.2 2002-03-02 17:23:55 thib Exp $";
void info(void);
void usage(void);
+void xexit(int exit_val);
+RETSIGTYPE sigpipe_handler(int x);
+int interactive_mode(int fd);
+/* returned by parse_cmd() and/or talk_fcron() */
+#define QUIT_CMD 1
+#define HELP_CMD 2
+#define ZEROLEN_CMD 3
+#define CMD_NOT_FOUND 4
+#define INVALID_ARG 5
+int talk_fcron(char *cmd_str, int fd);
+int parse_cmd(char *cmd_str, long int **cmd, int *cmd_len);
+int connect_fcron(void);
+int authenticate_user(int fd);
+/* command line options */
#ifdef DEBUG
char debug_opt = 1; /* set to 1 if we are in debug mode */
#else
char debug_opt = 0; /* set to 1 if we are in debug mode */
#endif
-
-char *user = NULL;
-uid_t uid = 0;
-uid_t asuid = 0;
+char *cmd_str = NULL;
/* needed by log part : */
char *prog_name = NULL;
char dosyslog = 1;
pid_t daemon_pid = 0;
+/* misc */
+char *user_str;
+uid_t user_uid;
+
+struct cmd_list_ent cmd_list[NUM_CMD] = {
+ /* name, desc, num opt, cmd code, cmd opts, cmd defaults */
+ {"ls_job", "List all jobs of user", 1, CMD_LIST_JOBS,
+ {USER}, {CUR_USER} },
+ {"ls_lavgq", "List jobs of user which are in lavg queue", 1, CMD_LIST_LAVGQ,
+ {USER}, {CUR_USER}},
+ {"ls_serialq", "List jobs of user which are in serial queue", 1, CMD_LIST_SERIALQ,
+ {USER}, {CUR_USER}},
+ {"ls_exeq", "List running jobs of user", 1, CMD_LIST_EXEQ,
+ {USER}, {CUR_USER}},
+ {"detail", "Print details on job", 1, CMD_DETAILS,
+ {JOBID}, {ARG_REQUIRED}},
+/* {"reschedule", "Reschedule next execution of job", 2, CMD_RESCHEDULE,
+ {TIME_AND_DATE, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}}, */
+ {"runnow", "Advance next execution of job to now", 1, CMD_RUNNOW,
+ {JOBID}, {ARG_REQUIRED}},
+ {"run", "Run job now (without changing its current schedule)", 1, CMD_RUN,
+ {JOBID}, {ARG_REQUIRED}},
+ {"send_signal", "Send signal to running job", 2, CMD_SEND_SIGNAL,
+ {SIGNAL, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}},
+ {"renice", "Renice running job", 2, CMD_RENICE,
+ {NICE_VALUE, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}}
+};
+
void
info(void)
/* print a help message about command line options and exit */
{
fprintf(stderr,
- "fcrondyn [-n] file [user|-u user]\n"
- "fcrondyn { -l | -r | -e | -z } [-n] [user|-u user]\n"
+ "fcrondyn [-i]\n"
+ "fcrondyn -x 'command'\n"
"fcrondyn -h\n"
- " -u user specify user name.\n"
- " -l list user's current fcrontab.\n"
- " -r remove user's current fcrontab.\n"
" -c f make fcrontab use config file f.\n"
" -d set up debug mode.\n"
" -h display this help message.\n"
+ " -V display version & infos about fcrondyn.\n"
"\n"
);
exit(EXIT_ERR);
}
+RETSIGTYPE
+sigpipe_handler(int x)
+ /* handle broken pipes ... */
+{
+ fprintf(stderr, "Broken pipe : check if fcron is still running.\n");
+ fprintf(stderr, "Exiting ...\n");
+
+ xexit(EXIT_ERR);
+}
+
+void
+xexit(int exit_val)
+ /* clean & exit */
+{
+ Flush(cmd_str);
+
+ exit(exit_val);
+}
+
+
+/* used in parse_cmd : */
+#define Write_cmd(data) \
+ memcpy(buf + *cmd_len, (long int *) &data, sizeof(long int)); \
+ *cmd_len += 1;
+
+#define Strncmp(str1, str2, str1_size) \
+ strncmp(str1, str2, (str1_size < strlen(str2)) ? strlen(str2) : str1_size)
+
+int
+parse_cmd(char *cmd_str, long int **cmd, int *cmd_len)
+ /* read a command string, check if it is valid and translate it */
+{
+ long int buf[SOCKET_MSG_LEN];
+ int word_size = 0;
+ int i = 0, rank = -1;
+ long int int_buf = 0;
+ struct passwd *pass = NULL;
+
+ bzero(buf, sizeof(buf));
+ *cmd_len = 0;
+ remove_blanks(cmd_str); /* at the end of the string */
+
+ if ( (word_size = get_word(&cmd_str)) == 0 ) {
+ fprintf(stderr, "Warning : Zero-length command name : line ignored.\n");
+ return ZEROLEN_CMD;
+ }
+
+ if (Strncmp(cmd_str, "q", word_size) == 0 || Strncmp(cmd_str, "quit", word_size) == 0
+ || Strncmp(cmd_str, "exit", word_size) == 0) {
+ if ( debug_opt )
+ fprintf(stderr, "quit command\n");
+ return QUIT_CMD;
+ }
+
+ if( Strncmp(cmd_str, "h", word_size)==0 || Strncmp(cmd_str, "help", word_size)==0 ) {
+ if ( debug_opt )
+ fprintf(stderr, "help command\n");
+ return HELP_CMD;
+ }
+
+ for (i = 0; i < NUM_CMD; i++) {
+ if ( Strncmp(cmd_str, cmd_list[i].cmd_name, word_size) == 0 ) {
+ rank = i;
+ break;
+ }
+ }
+ if ( rank == (-1) ) {
+ fprintf(stderr, "Error : Command not found.\n");
+ return CMD_NOT_FOUND;
+ }
+ Write_cmd(cmd_list[rank].cmd_code);
+
+ if ( debug_opt )
+ fprintf(stderr, "command : %s\n", cmd_list[i].cmd_name);
+
+ cmd_str += word_size;
+ for (i = 0 ; i < cmd_list[rank].cmd_numopt; i++) {
+
+ if ( (word_size = get_word(&cmd_str)) == 0 ) {
+
+ if (cmd_list[rank].cmd_default[i] == ARG_REQUIRED) {
+ fprintf(stderr, "Error : arg required !\n");
+ return INVALID_ARG;
+ }
+
+ /* use default value : currently, works only with CUR_USER */
+ if ( user_uid == ROOTUID ) {
+ /* default for root = all */
+ int_buf = ALL;
+ Write_cmd( int_buf );
+ if ( debug_opt )
+ fprintf(stderr, " uid = ALL\n");
+ }
+ else {
+ Write_cmd( user_uid );
+ if ( debug_opt )
+ fprintf(stderr, " uid = %d\n", user_uid);
+ }
+
+ }
+
+ else {
+
+ /* get value from line ... */
+ switch (cmd_list[rank].cmd_opt[i]) {
+
+ case USER:
+ int_buf = (long int) *(cmd_str + word_size);
+ *(cmd_str + word_size) = '\0';
+ if ( (pass = getpwnam(cmd_str) ) == NULL ) {
+ fprintf(stderr, "Error : '%s' is not a valid user name.\n", cmd_str);
+ return INVALID_ARG;
+ }
+ *(cmd_str + word_size) = (char) int_buf;
+ cmd_str += word_size;
+ Write_cmd(pass->pw_uid);
+ if ( debug_opt )
+ fprintf(stderr, " uid = %d\n", pass->pw_uid);
+ break;
+
+ case JOBID:
+ /* after strtol(), cmd_str will be updated (first non-number char) */
+ if ( (int_buf = strtol(cmd_str, &cmd_str, 10)) < 0 || int_buf >= LONG_MAX
+ || (! isspace( (int) *cmd_str) && *cmd_str != '\0') ) {
+ fprintf(stderr, "Error : invalid jobid.\n");
+ return INVALID_ARG;
+ }
+ Write_cmd(int_buf);
+ if ( debug_opt )
+ fprintf(stderr, " jobid = %ld\n", int_buf);
+ break;
+
+ case TIME_AND_DATE:
+ /* argghh !!! no standard function ! */
+ break;
+
+ case NICE_VALUE:
+ /* after strtol(), cmd_str will be updated (first non-number char) */
+ if ( (int_buf = strtol(cmd_str, &cmd_str, 10)) > 20
+ || (int_buf < 0 && getuid() != ROOTUID) || int_buf < -20
+ || (! isspace( (int) *cmd_str) && *cmd_str != '\0') ) {
+ fprintf(stderr, "Error : invalid nice value.\n");
+ return INVALID_ARG;
+ }
+ Write_cmd(int_buf);
+ if ( debug_opt )
+ fprintf(stderr, " nicevalue = %ld\n", int_buf);
+ break;
+
+ case SIGNAL:
+ if ( isalpha( (int) *cmd_str ) ) {
+ if ( Strncmp(cmd_str, "HUP", word_size) == 0 ) int_buf = SIGHUP;
+ else if (Strncmp(cmd_str, "INT", word_size) == 0) int_buf = SIGINT;
+ else if (Strncmp(cmd_str, "QUIT", word_size) == 0) int_buf = SIGQUIT;
+ else if (Strncmp(cmd_str, "KILL", word_size) == 0) int_buf = SIGKILL;
+ else if (Strncmp(cmd_str, "ALRM", word_size) == 0) int_buf = SIGALRM;
+ else if (Strncmp(cmd_str, "TERM", word_size) == 0) int_buf = SIGTERM;
+ else if (Strncmp(cmd_str, "USR1", word_size) == 0) int_buf = SIGUSR1;
+ else if (Strncmp(cmd_str, "USR2", word_size) == 0) int_buf = SIGUSR2;
+ else if (Strncmp(cmd_str, "CONT", word_size) == 0) int_buf = SIGCONT;
+ else if (Strncmp(cmd_str, "STOP", word_size) == 0) int_buf = SIGSTOP;
+ else if (Strncmp(cmd_str, "TSTP", word_size) == 0) int_buf = SIGTSTP;
+ else {
+ fprintf(stderr, "Error : unknow signal (try integer value)\n");
+ return INVALID_ARG;
+ }
+ cmd_str += word_size;
+ }
+ /* after strtol(), cmd_str will be updated (first non-number char) */
+ else if((int_buf=strtol(cmd_str, &cmd_str, 10)) < 0 || int_buf>=LONG_MAX
+ || (! isspace( (int) *cmd_str) && *cmd_str != '\0') ) {
+ fprintf(stderr, "Error : invalid signal value.\n");
+ return INVALID_ARG;
+ }
+ Write_cmd(int_buf);
+ if ( debug_opt )
+ fprintf(stderr, " signal = %ld\n", int_buf);
+ break;
+
+ default:
+ fprintf(stderr, "Error : Unknown arg !");
+ return INVALID_ARG;
+ }
+ }
+ }
+
+ Skip_blanks(cmd_str);
+ if ( *cmd_str != '\0' )
+ fprintf(stderr, "Warning : too much arguments : '%s' ignored.\n", cmd_str);
+
+ /* This is a valid command ... */
+ if ( (*cmd = calloc(*cmd_len, sizeof(long int))) == NULL )
+ die_e("Could not calloc.");
+
+ memcpy(*cmd, buf, *cmd_len * sizeof(long int));
+
+ return OK;
+}
+
+int
+authenticate_user(int fd)
+ /* authenticate user */
+{
+ char *password = NULL;
+ char buf[USER_NAME_LEN + 16];
+ int len = 0;
+
+ snprintf(buf, sizeof(buf), "password for %s :", user_str);
+ if ( (password = read_string(CONV_ECHO_OFF, buf)) == NULL )
+ return ERR;
+
+ len = snprintf(buf, sizeof(buf), "%s", user_str) + 1;
+ len += snprintf(buf + len, sizeof(buf) - len, "%s", password) + 1;
+ send(fd, buf, len, 0);
+ Overwrite(buf);
+ Overwrite(password);
+ free(password);
+ recv(fd, buf, sizeof(buf), 0);
+ if ( strncmp(buf, "1", sizeof("1")) != 0 )
+ return ERR;
+
+ return OK;
+}
+
+
+int
+connect_fcron(void)
+ /* connect to fcron through a socket, and identify user */
+{
+ int fd = -1;
+ struct sockaddr_un addr;
+ int len = 0;
+
+ if ( (fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1 )
+ die_e("could not create socket");
+
+ addr.sun_family = AF_UNIX;
+ if ( (len = strlen(fifofile)) > sizeof(addr.sun_path) )
+ die("Error : fifo file path too long (max is %d)", sizeof(addr.sun_path));
+ strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path));
+
+ if ( connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + len) < 0 )
+ die_e("Cannot connect() to fcron (check if fcron is running)");
+
+ /* */
+ //if ( fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1 )
+ //error_e("Could not set fd attribute O_NONBLOCK");
+ /* */
+
+ if ( authenticate_user(fd) == ERR )
+ die("Unable to authenticate user");
+
+ return fd;
+
+}
+
+int
+talk_fcron(char *cmd_str, int fd)
+ /* read a string command, check if it is valid and translate it,
+ * send it to fcron, and print its answer */
+{
+ long int *cmd = NULL;
+ int cmd_len = 0;
+ char buf[LINE_LEN];
+ size_t read_len = 0;
+ char existing_connection = (fd < 0) ? 0 : 1;
+
+ switch ( parse_cmd(cmd_str, &cmd, &cmd_len) ) {
+ case OK:
+ break;
+ case HELP_CMD:
+ {
+ int i, j;
+ printf("Command recognized by fcrondyn :\n");
+ printf("------------------------------\n");
+ for (i = 0; i < NUM_CMD; i++) {
+ printf("%s ", cmd_list[i].cmd_name);
+
+ /* print args : */
+ for (j = 0; j < cmd_list[i].cmd_numopt; j++) {
+ if ( cmd_list[i].cmd_default[j] != ARG_REQUIRED )
+ printf("[");
+ switch ( cmd_list[i].cmd_opt[j] ) {
+ case USER: printf("user"); break;
+ case JOBID: printf("jobid"); break;
+ case TIME_AND_DATE: printf("time"); break;
+ case NICE_VALUE: printf("niceval"); break;
+ case SIGNAL: printf("sig"); break;
+ case BOOLEAN: printf("bool"); break;
+ default: printf("unknown_arg!");
+ }
+ if ( cmd_list[i].cmd_default[j] != ARG_REQUIRED )
+ printf("]");
+ printf(" ");
+ }
+
+ if (cmd_list[i].cmd_numopt + (strlen(cmd_list[i].cmd_name)/8) <= 1.5)
+ printf("\t\t%s\n", cmd_list[i].cmd_desc);
+ else
+ printf("\t%s\n", cmd_list[i].cmd_desc);
+ }
+ }
+ return HELP_CMD;
+ case QUIT_CMD:
+ return QUIT_CMD;
+ case CMD_NOT_FOUND:
+ return CMD_NOT_FOUND;
+ case INVALID_ARG:
+ return INVALID_ARG;
+ case ZEROLEN_CMD:
+ return ZEROLEN_CMD;
+ default:
+ return ERR;
+ }
+
+ /* This is a valid command (so we'll have to free() it) ... */
+
+ if ( ! existing_connection && (fd = connect_fcron()) == ERR )
+ return ERR;
+
+ send(fd, cmd, cmd_len * sizeof(long int), 0);
+ Flush(cmd);
+ cmd_len = 0;
+
+ while ( (read_len = recv(fd, buf, sizeof(buf), 0)) > 0 && read_len < sizeof(buf) ) {
+ write(STDOUT_FILENO, buf, read_len);
+ if ( read_len > sizeof(END_STR) &&
+ strncmp( &buf[read_len - sizeof(END_STR)], END_STR, sizeof(END_STR)) == 0 )
+ break;
+ }
+
+
+ if ( ! existing_connection )
+ close(fd);
+
+ return OK;
+}
+
+
+int
+interactive_mode(int fd)
+ /* provide user a prompt, read command, send it to fcron, print its answer,
+ * then give another prompt, etc, until user type an exit command */
+{
+ char existing_connection = (fd < 0) ? 0 : 1;
+ int return_code = 0;
+ char buf[LINE_LEN];
+
+ if ( ! existing_connection && (fd = connect_fcron()) == ERR )
+ return ERR;
+
+ while (fprintf(stderr, "fcrondyn> ") && fgets(buf, sizeof(buf), stdin) != NULL
+ && (return_code = talk_fcron(buf, fd)) != QUIT_CMD) ;
+
+ if ( ! existing_connection )
+ close(fd);
+
+ return OK;
+}
+
+
+void
+parseopt(int argc, char *argv[])
+ /* set options */
+{
+
+ int c, i;
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+
+ /* constants and variables defined by command line */
+
+ while(1) {
+ c = getopt(argc, argv, "hVdc:ix:");
+ if (c == EOF) break;
+ switch (c) {
+
+ case 'V':
+ info(); break;
+
+ case 'h':
+ usage(); break;
+
+ case 'd':
+ debug_opt = 1; break;
+
+ case 'c':
+ Set(fcronconf, optarg);
+ break;
+
+ case 'i':
+ Flush(cmd_str);
+ break;
+
+ case 'x':
+ Set(cmd_str, optarg);
+ break;
+
+ case ':':
+ fprintf(stderr, "(setopt) Missing parameter.\n");
+ usage();
+
+ case '?':
+ usage();
+
+ default:
+ fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
+ }
+
+ }
+
+ if (optind < argc) {
+ for (i = optind; i <= argc; i++)
+ fprintf(stderr, "Unknown argument \"%s\"", argv[i]);
+ usage();
+ }
+}
+
+
int
main(int argc, char **argv)
{
+ int return_code = 0;
+ int fd = (-1);
+ struct passwd *pass = NULL;
+
+ if ( strrchr(argv[0], '/') == NULL) prog_name = argv[0];
+ else prog_name = strrchr(argv[0], '/') + 1;
+
+ /* interpret command line options */
+ parseopt(argc, argv);
+
+/* if (debug_opt) */
+/* fprintf(stderr, "uid : %d euid : %d\n", getuid(), geteuid()); */
+
+ /* read fcron.conf and update global parameters */
+ read_conf();
+
+ user_uid = getuid();
+ if ( (pass = getpwuid(user_uid)) == NULL )
+ die("user \"%s\" is not in passwd file. Aborting.", USERNAME);
+ user_str = strdup2(pass->pw_name);
+
+ /* we don't need anymore special rights : drop them */
+ seteuid(user_uid);
+ setuid(user_uid);
+/* if (debug_opt) */
+/* fprintf(stderr, "uid : %d euid : %d\n", getuid(), geteuid()); */
+
+ if ( ! is_allowed(user_str) ) {
+ die("User \"%s\" is not allowed to use %s. Aborting.",
+ user_str, prog_name);
+ }
+
+ /* check for broken pipes ... */
+ signal(SIGPIPE, sigpipe_handler);
+
+ if ( cmd_str == NULL )
+ return_code = interactive_mode(fd);
+ else
+ return_code = talk_fcron(cmd_str, fd);
+
+ xexit( (return_code == OK ) ? EXIT_OK : EXIT_ERR );
+
+ /* never reached */
+ return EXIT_OK;
}