]> granicus.if.org Git - fcron/blob - fcrondyn.c
fixed typo in install doc
[fcron] / fcrondyn.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2014 Thibault Godouet <fcron@free.fr>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  * 
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 /* fcrondyn : interact dynamically with running fcron process :
26  *     - list jobs, with their status, next time of execution, etc
27  *     - run, delay a job
28  *     - send a signal to a job
29  *     - etc ...
30  */
31
32 #include "fcrondyn.h"
33 #include "allow.h"
34 #include "read_string.h"
35 #include "mem.h"
36
37 #ifdef HAVE_LIBREADLINE
38 char **rl_cmpl_fcrondyn(const char *text, int start, int end);
39 char *rl_cmpl_command_generator(const char *text, int state);
40 #if defined(HAVE_READLINE_READLINE_H)
41 #include <readline/readline.h>
42 #elif defined(HAVE_READLINE_H)
43 #include <readline.h>
44 #endif                          /* !defined(HAVE_READLINE_H) */
45 #if defined(HAVE_READLINE_HISTORY_H)
46 #include <readline/history.h>
47 #elif defined(HAVE_HISTORY_H)
48 #include <history.h>
49 #endif                          /* defined(HAVE_READLINE_HISTORY_H) */
50 #endif                          /* HAVE_LIBREADLINE */
51
52 void info(void);
53 void usage(void);
54 void xexit(int exit_val);
55 RETSIGTYPE sigpipe_handler(int x);
56 int interactive_mode(int fd);
57 /* returned by parse_cmd() and/or talk_fcron() */
58 #define QUIT_CMD 1
59 #define HELP_CMD 2
60 #define ZEROLEN_CMD 3
61 #define CMD_NOT_FOUND 4
62 #define INVALID_ARG 5
63 int talk_fcron(char *cmd_str, int fd);
64 int parse_cmd(char *cmd_str, long int **cmd, int *cmd_len);
65 int connect_fcron(void);
66 int authenticate_user_password(int fd);
67
68 /* command line options */
69 char *cmd_str = NULL;
70
71 /* needed by log part : */
72 char *prog_name = NULL;
73 char foreground = 1;
74 pid_t daemon_pid = 0;
75
76 /* uid/gid of user/group root 
77  * (we don't use the static UID or GID as we ask for user and group names
78  * in the configure script) */
79 uid_t rootuid = 0;
80 gid_t rootgid = 0;
81
82 /* misc */
83 char *user_str = NULL;
84 uid_t user_uid = 0;
85 gid_t user_gid = 0;
86
87 struct cmd_list_ent cmd_list[] = {
88     /* name, desc, num opt, cmd code, cmd opts, cmd defaults */
89     {"ls", {NULL}, "List all jobs of user", 1, CMD_LIST_JOBS,
90      {USER}, {CUR_USER}},
91     {"ls_lavgq", {}, "List jobs of user which are in lavg queue", 1,
92      CMD_LIST_LAVGQ, {USER}, {CUR_USER}},
93     {"ls_serialq", {}, "List jobs of user which are in serial queue",
94      1, CMD_LIST_SERIALQ, {USER}, {CUR_USER}},
95     {"ls_exeq", {}, "List running jobs of user", 1, CMD_LIST_EXEQ,
96      {USER}, {CUR_USER}},
97     {"detail", {}, "Print details on job", 1, CMD_DETAILS,
98      {JOBID}, {ARG_REQUIRED}},
99 /*    {"reschedule", {NULL}, "Reschedule next execution of job", 2,
100       CMD_RESCHEDULE, {TIME_AND_DATE, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}}, */
101     {"runnow", {}, "Advance next execution of job to now", 1, CMD_RUNNOW,
102      {JOBID}, {ARG_REQUIRED}},
103     {"run", {}, "Run job now (without changing its current schedule)", 1,
104      CMD_RUN, {JOBID}, {ARG_REQUIRED}},
105     {"kill", {}, "Send signal to running job", 2, CMD_SEND_SIGNAL,
106      {SIGNAL, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}},
107     {"renice", {}, "Renice running job", 2, CMD_RENICE,
108      {NICE_VALUE, JOBID}, {ARG_REQUIRED, ARG_REQUIRED}},
109     {"quit", {"q", "exit"}, "Quit fcrondyn", 0, QUIT_CMD, {}, {}},
110     {"help", {"h"}, "Display a help message", 0, HELP_CMD, {}, {}},
111 };
112
113 const int cmd_list_len = sizeof(cmd_list) / sizeof(cmd_list_ent);
114
115 void
116 info(void)
117     /* print some informations about this program :
118      * version, license */
119 {
120     fprintf(stderr,
121             "fcrondyn " VERSION_QUOTED
122             " - interact dynamically with daemon fcron\n" "Copyright "
123             COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
124             "This program is free software distributed WITHOUT ANY WARRANTY.\n"
125             "See the GNU General Public License for more details.\n");
126
127     exit(EXIT_OK);
128
129 }
130
131
132 void
133 usage(void)
134   /*  print a help message about command line options and exit */
135 {
136     fprintf(stderr,
137             "fcrondyn [-i]\n"
138             "fcrondyn -x {command}\n"
139             "fcrondyn -h\n"
140             "  -i         run fcrontab in interactive mode.\n"
141             "  -x         execute one command (in batch mode)\n"
142             "  -c f       make fcrontab use config file f.\n"
143             "  -d         set up debug mode.\n"
144             "  -h         display this help message.\n"
145             "  -V         display version & infos about fcrondyn.\n" "\n"
146             "To list the available commands, run:\n" "  fcrondyn -x help\n");
147
148     exit(EXIT_ERR);
149 }
150
151 RETSIGTYPE
152 sigpipe_handler(int x)
153     /* handle broken pipes ... */
154 {
155     fprintf(stderr,
156             "Broken pipe : fcron may have closed the connection\nThe connection "
157             "has been idle for more than %ds, or fcron may not be running anymore.\n",
158             MAX_IDLE_TIME);
159     fprintf(stderr, "Exiting ...\n");
160
161     xexit(EXIT_ERR);
162 }
163
164 void
165 xexit(int exit_val)
166     /* clean & exit */
167 {
168     Free_safe(cmd_str);
169
170     exit(exit_val);
171 }
172
173
174 /* used in parse_cmd : */
175 #define Write_cmd(DATA) \
176           memcpy(buf + *cmd_len, &DATA, sizeof(long int)); \
177           *cmd_len += 1;
178
179 #define Strncmp(STR1, STR2, STR1_SIZE) \
180           strncmp(STR1, STR2, (STR1_SIZE < strlen(STR2)) ? strlen(STR2) : STR1_SIZE)
181
182 int
183 parse_cmd(char *cmd_str, long int **cmd, int *cmd_len)
184     /* read a command string, check if it is valid and translate it */
185 {
186     long int buf[SOCKET_MSG_LEN];
187     int word_size = 0;
188     int i = 0, j = 0, rank = -1;
189     long int int_buf = 0;
190     struct passwd *pass = NULL;
191 #ifdef SYSFCRONTAB
192     long int sysfcrontab_uid = SYSFCRONTAB_UID;
193 #endif
194
195     bzero(buf, sizeof(buf));
196     *cmd_len = 0;
197     remove_blanks(cmd_str);     /* at the end of the string */
198
199     if ((word_size = get_word(&cmd_str)) == 0) {
200         fprintf(stderr, "Warning : Zero-length command name : line ignored.\n");
201         return ZEROLEN_CMD;
202     }
203
204     for (i = 0; i < cmd_list_len; i++) {
205         int j;
206         if (Strncmp(cmd_str, cmd_list[i].cmd_name, word_size) == 0) {
207             rank = i;
208             break;
209         }
210         for (j = 0; j < MAX_NUM_ALIAS && cmd_list[i].cmd_alias[j] != NULL; j++) {
211             if (Strncmp(cmd_str, cmd_list[i].cmd_alias[j], word_size) == 0) {
212                 rank = i;
213                 break;
214             }
215         }
216     }
217     if (rank == (-1)) {
218         fprintf(stderr, "Error : Unknown command.\n");
219         return CMD_NOT_FOUND;
220     }
221     else if (cmd_list[rank].cmd_code == QUIT_CMD) {
222         if (debug_opt)
223             fprintf(stderr, "quit command\n");
224         return QUIT_CMD;
225     }
226     else if (cmd_list[rank].cmd_code == HELP_CMD) {
227         if (debug_opt)
228             fprintf(stderr, "Help command\n");
229         return HELP_CMD;
230     }
231
232     Write_cmd(cmd_list[rank].cmd_code);
233
234     if (debug_opt)
235         fprintf(stderr, "command : %s\n", cmd_list[i].cmd_name);
236
237     cmd_str += word_size;
238     for (i = 0; i < cmd_list[rank].cmd_numopt; i++) {
239
240         if ((word_size = get_word(&cmd_str)) == 0) {
241
242             if (cmd_list[rank].cmd_default[i] == ARG_REQUIRED) {
243                 fprintf(stderr, "Error : arg required !\n");
244                 return INVALID_ARG;
245             }
246
247             /* use default value : currently, works only with CUR_USER */
248             if (user_uid == rootuid) {
249                 /* default for root = all */
250                 int_buf = ALL;
251                 Write_cmd(int_buf);
252                 if (debug_opt)
253                     fprintf(stderr, "  uid = ALL\n");
254             }
255             else {
256                 Write_cmd(user_uid);
257                 if (debug_opt)
258                     fprintf(stderr, "  uid = %d\n", (int)user_uid);
259             }
260
261         }
262
263         else {
264
265             /* get value from line ... */
266             switch (cmd_list[rank].cmd_opt[i]) {
267
268             case USER:
269                 int_buf = (long int)*(cmd_str + word_size);
270                 *(cmd_str + word_size) = '\0';
271 #ifdef SYSFCRONTAB
272                 if (strcmp(cmd_str, SYSFCRONTAB) == 0) {
273                     Write_cmd(sysfcrontab_uid);
274                 }
275                 else {
276 #endif
277                     if ((pass = getpwnam(cmd_str)) == NULL) {
278                         fprintf(stderr,
279                                 "Error : '%s' isn't a valid username.\n",
280                                 cmd_str);
281                         return INVALID_ARG;
282                     }
283                     Write_cmd(pass->pw_uid);
284 #ifdef SYSFCRONTAB
285                 }
286 #endif
287                 *(cmd_str + word_size) = (char)int_buf;
288                 cmd_str += word_size;
289                 if (debug_opt)
290                     fprintf(stderr, "  uid = %d\n",
291 #ifdef SYSFCRONTAB
292                             (pass) ? (int)pass->pw_uid : (int)SYSFCRONTAB_UID
293 #else
294                             (int)pass->pw_uid
295 #endif
296                         );
297                 break;
298
299             case JOBID:
300                 /* after strtol(), cmd_str will be updated (first non-number char) */
301                 if ((int_buf = strtol(cmd_str, &cmd_str, 10)) < 0
302                     || int_buf >= LONG_MAX || (!isspace((int)*cmd_str)
303                                                && *cmd_str != '\0')) {
304                     fprintf(stderr, "Error : invalid jobid.\n");
305                     return INVALID_ARG;
306                 }
307                 Write_cmd(int_buf);
308                 if (debug_opt)
309                     fprintf(stderr, "  jobid = %ld\n", int_buf);
310                 break;
311
312             case TIME_AND_DATE:
313                 /* argghh !!! no standard function ! */
314                 break;
315
316             case NICE_VALUE:
317                 /* after strtol(), cmd_str will be updated (first non-number char) */
318                 if ((int_buf = strtol(cmd_str, &cmd_str, 10)) > 20
319                     || (int_buf < 0 && getuid() != rootuid) || int_buf < -20
320                     || (!isspace((int)*cmd_str) && *cmd_str != '\0')) {
321                     fprintf(stderr, "Error : invalid nice value.\n");
322                     return INVALID_ARG;
323                 }
324                 Write_cmd(int_buf);
325                 if (debug_opt)
326                     fprintf(stderr, "  nicevalue = %ld\n", int_buf);
327                 break;
328
329             case SIGNAL:
330                 if (isalpha((int)*cmd_str)) {
331                     for (j = 0; j < word_size; j++)
332                         *(cmd_str + j) = tolower(*(cmd_str + j));
333                     if (Strncmp(cmd_str, "hup", word_size) == 0)
334                         int_buf = SIGHUP;
335                     else if (Strncmp(cmd_str, "int", word_size) == 0)
336                         int_buf = SIGINT;
337                     else if (Strncmp(cmd_str, "quit", word_size) == 0)
338                         int_buf = SIGQUIT;
339                     else if (Strncmp(cmd_str, "kill", word_size) == 0)
340                         int_buf = SIGKILL;
341                     else if (Strncmp(cmd_str, "alrm", word_size) == 0)
342                         int_buf = SIGALRM;
343                     else if (Strncmp(cmd_str, "term", word_size) == 0)
344                         int_buf = SIGTERM;
345                     else if (Strncmp(cmd_str, "usr1", word_size) == 0)
346                         int_buf = SIGUSR1;
347                     else if (Strncmp(cmd_str, "usr2", word_size) == 0)
348                         int_buf = SIGUSR2;
349                     else if (Strncmp(cmd_str, "cont", word_size) == 0)
350                         int_buf = SIGCONT;
351                     else if (Strncmp(cmd_str, "stop", word_size) == 0)
352                         int_buf = SIGSTOP;
353                     else if (Strncmp(cmd_str, "tstp", word_size) == 0)
354                         int_buf = SIGTSTP;
355                     else {
356                         fprintf(stderr,
357                                 "Error : unknow signal (try integer value)\n");
358                         return INVALID_ARG;
359                     }
360                     cmd_str += word_size;
361                 }
362                 /* after strtol(), cmd_str will be updated (first non-number char) */
363                 else if ((int_buf = strtol(cmd_str, &cmd_str, 10)) <= 0
364                          || int_buf >= LONG_MAX || (!isspace((int)*cmd_str)
365                                                     && *cmd_str != '\0')) {
366                     fprintf(stderr, "Error : invalid signal value.\n");
367                     return INVALID_ARG;
368                 }
369                 Write_cmd(int_buf);
370                 if (debug_opt)
371                     fprintf(stderr, "  signal = %ld\n", int_buf);
372                 break;
373
374             default:
375                 fprintf(stderr, "Error : Unknown arg !");
376                 return INVALID_ARG;
377             }
378         }
379     }
380
381     Skip_blanks(cmd_str);
382     if (*cmd_str != '\0')
383         fprintf(stderr, "Warning : too much arguments : '%s' ignored.\n",
384                 cmd_str);
385
386     /* This is a valid command ... */
387     *cmd = alloc_safe(*cmd_len * sizeof(long int), "command string");
388     memcpy(*cmd, buf, *cmd_len * sizeof(long int));
389
390     return OK;
391 }
392
393 int
394 authenticate_user_password(int fd)
395     /* authenticate user */
396 {
397     char *password = NULL;
398     char buf[USER_NAME_LEN + 16];
399     int len = 0;
400     fd_set read_set;            /* needed to use select to check if some data is waiting */
401     struct timeval tv;
402
403     snprintf(buf, sizeof(buf), "password for %s :", user_str);
404     if ((password = read_string(CONV_ECHO_OFF, buf)) == NULL)
405         return ERR;
406
407     len = snprintf(buf, sizeof(buf), "%s", user_str) + 1;
408     len += snprintf(buf + len, sizeof(buf) - len, "%s", password) + 1;
409     send(fd, buf, len, 0);
410     Overwrite(buf);
411     Overwrite(password);
412     Free_safe(password);
413
414     tv.tv_sec = MAX_WAIT_TIME;
415     tv.tv_usec = 0;
416     FD_ZERO(&read_set);
417     FD_SET(fd, &read_set);
418     if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) {
419         error_e("Couldn't get data from socket during %d seconds.",
420                 MAX_WAIT_TIME);
421         return ERR;
422     }
423     while (recv(fd, buf, sizeof(buf), 0) < 0 && errno == EINTR)
424         if (errno == EINTR && debug_opt)
425             fprintf(stderr, "Got EINTR ...");
426     if (strncmp(buf, "1", sizeof("1")) != 0)
427         return ERR;
428
429     return OK;
430 }
431
432
433 int
434 connect_fcron(void)
435     /* connect to fcron through a socket, and identify user */
436 {
437     int fd = -1;
438     struct sockaddr_un addr;
439     int len = 0;
440     int sun_len = 0;
441
442     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
443         die_e("could not create socket");
444
445     addr.sun_family = AF_UNIX;
446     len = strlen(fifofile);
447     if (len > sizeof(addr.sun_path) - 1)
448         die("Error : fifo file path too long (max is %d)",
449             sizeof(addr.sun_path) - 1);
450     /* length(fifofile) < sizeof(add.sun_path), so strncpy will terminate
451      * the string with at least one \0 (not necessarily required by the OS,
452      * but still a good idea) */
453     strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path));
454     addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
455     sun_len = (addr.sun_path - (char *)&addr) + len;
456 #if HAVE_SA_LEN
457     addr.sun_len = sun_len;
458 #endif
459
460     if (connect(fd, (struct sockaddr *)&addr, sun_len) < 0)
461         die_e("Cannot connect() to fcron (check if fcron is running)");
462
463 /* Nothing to do on the client side if we use SO_PASSCRED */
464 #if !defined(SO_PASSCRED) && !defined(HAVE_GETPEERUCRED) && !defined(HAVE_GETPEEREID)
465     if (authenticate_user_password(fd) == ERR) {
466         fprintf(stderr, "Invalid password or too many authentication failures"
467                 " (try to connect later).\n(In the later case, fcron rejects all"
468                 " new authentication during %d secs)\n", AUTH_WAIT);
469         die("Unable to authenticate user");
470     }
471 #endif                          /* SO_PASSCRED HAVE_GETPEERUCRED HAVE_GETPEEREID */
472
473     return fd;
474
475 }
476
477 int
478 talk_fcron(char *cmd_str, int fd)
479     /* read a string command, check if it is valid and translate it,
480      * send it to fcron, and print its answer */
481 {
482     long int *cmd = NULL;
483     int cmd_len = 0;
484     char buf[LINE_LEN];
485     size_t read_len = 0;
486     char existing_connection = (fd < 0) ? 0 : 1;
487     fd_set read_set;            /* needed to use select to check if some data is waiting */
488     struct timeval tv;
489
490     switch (parse_cmd(cmd_str, &cmd, &cmd_len)) {
491     case OK:
492         break;
493     case HELP_CMD:
494         {
495             int i, j, len;
496             printf("Command recognized by fcrondyn :\n");
497             printf("------------------------------\n");
498             for (i = 0; i < cmd_list_len; i++) {
499                 len = printf("%s ", cmd_list[i].cmd_name);
500
501                 /* print args : */
502                 for (j = 0; j < cmd_list[i].cmd_numopt; j++) {
503                     if (cmd_list[i].cmd_default[j] != ARG_REQUIRED)
504                         len += printf("[");
505                     switch (cmd_list[i].cmd_opt[j]) {
506                     case USER:
507                         len += printf("user");
508                         break;
509                     case JOBID:
510                         len += printf("jobid");
511                         break;
512                     case TIME_AND_DATE:
513                         len += printf("time");
514                         break;
515                     case NICE_VALUE:
516                         len += printf("niceval");
517                         break;
518                     case SIGNAL:
519                         len += printf("sig");
520                         break;
521                     case BOOLEAN:
522                         len += printf("bool");
523                         break;
524                     default:
525                         len += printf("unknown_arg!");
526                     }
527                     if (cmd_list[i].cmd_default[j] != ARG_REQUIRED)
528                         len += printf("]");
529                     len += printf(" ");
530                 }
531                 /* Align correctly the descriptions : */
532                 printf("%*s%s", 24 - len, "", cmd_list[i].cmd_desc);
533
534                 /* print alias list (if any) */
535                 if (cmd_list[i].cmd_alias[0] != NULL) {
536                     printf(" (aliases:");
537                     for (j = 0;
538                          j < MAX_NUM_ALIAS && cmd_list[i].cmd_alias[j] != NULL;
539                          j++) {
540                         printf(" %s", cmd_list[i].cmd_alias[j]);
541                     }
542                     printf(")");
543                 }
544
545                 printf("\n");
546             }
547         }
548         return HELP_CMD;
549     case QUIT_CMD:
550         return QUIT_CMD;
551     case CMD_NOT_FOUND:
552         return CMD_NOT_FOUND;
553     case INVALID_ARG:
554         return INVALID_ARG;
555     case ZEROLEN_CMD:
556         return ZEROLEN_CMD;
557     default:
558         return ERR;
559     }
560
561     /* This is a valid command (so we'll have to free() it) ... */
562
563     if (!existing_connection && (fd = connect_fcron()) == ERR)
564         return ERR;
565
566     send(fd, cmd, cmd_len * sizeof(long int), 0);
567     Free_safe(cmd);
568     cmd_len = 0;
569
570     tv.tv_sec = MAX_WAIT_TIME;
571     tv.tv_usec = 0;
572     FD_ZERO(&read_set);
573     FD_SET(fd, &read_set);
574     if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) {
575         error_e("Couldn't get data from socket during %d seconds.",
576                 MAX_WAIT_TIME);
577         return ERR;
578     }
579
580
581     while ((read_len = (size_t) recv(fd, buf, sizeof(buf) - 1, 0)) >= 0
582            || errno == EINTR) {
583
584         if (errno == EINTR && debug_opt)
585             fprintf(stderr, "got EINTR ...\n");
586         else if (read_len > sizeof(buf)) {
587             /* weird ... no data yet ? */
588             if (debug_opt)
589                 fprintf(stderr, "no data yet ?");
590         }
591         else if (read_len <= 0) {
592             if (debug_opt)
593                 fprintf(stderr, "read_len = %d\n", (int)read_len);
594             fprintf(stderr, "connection closed by fcron\n");
595             shutdown(fd, SHUT_RDWR);
596             return ERR;
597         }
598         else {
599             /* ensure the string is terminated by a '\0' for when we'll printf() it */
600             buf[read_len] = '\0';
601             printf("%s", buf);
602
603             /* check for the end of command output marker */
604             if (read_len >= sizeof(END_STR) &&
605                 strncmp(&buf[read_len - sizeof(END_STR)], END_STR,
606                         sizeof(END_STR)) == 0)
607                 break;
608         }
609     }
610     if (read_len < 0) {
611         error_e("error in recv()");
612     }
613
614     if (!existing_connection)
615         xclose_check(&fd, "unix socket");
616
617     return OK;
618 }
619
620 #ifdef HAVE_LIBREADLINE
621 /* Attempt to complete on the contents of TEXT. START and END bound the
622    region of rl_line_buffer that contains the word to complete. TEXT is
623    the word to complete. We can use the entire contents of rl_line_buffer
624    in case we want to do some simple parsing. Return the array of matches,
625    or NULL if there aren't any. */
626 char **
627 rl_cmpl_fcrondyn(const char *text, int start, int end)
628 {
629     char **matches;
630
631     matches = (char **)NULL;
632
633     /* If this word is at the start of the line, then it is a command
634      * to complete. Otherwise it is an argument which we ignore for now */
635     if (start == 0) {
636         matches = rl_completion_matches(text, rl_cmpl_command_generator);
637     }
638
639     return (matches);
640 }
641
642 /* Generator function for command completion. STATE lets us know whether
643    to start from scratch; without any state (i.e. STATE == 0), then we
644    start at the top of the list. */
645 char *
646 rl_cmpl_command_generator(const char *text, int state)
647 {
648     static int list_index, len;
649     char *name = NULL;
650
651     /* If this is a new word to complete, initialize now.  This includes
652      * saving the length of TEXT for efficiency, and initializing the index
653      * variable to 0. */
654     if (!state) {
655         list_index = 0;
656         len = strlen(text);
657     }
658
659     /* Return the next name which partially matches from the command list. */
660     while (list_index < cmd_list_len) {
661         name = cmd_list[list_index].cmd_name;
662         list_index++;
663         if (strncmp(name, text, len) == 0) {
664             return (strdup2(name));
665         }
666     }
667
668     /* If no names matched, then return NULL. */
669     return ((char *)NULL);
670 }
671 #endif                          /* HAVE_LIBREADLINE */
672
673 int
674 interactive_mode(int fd)
675     /* provide user a prompt, read command, send it to fcron, print its answer,
676      * then give another prompt, etc, until user type an exit command */
677 {
678     char existing_connection = (fd < 0) ? 0 : 1;
679     int return_code = 0;
680 #ifdef HAVE_LIBREADLINE
681     char *line_read = NULL;
682 #else                           /* HAVE_LIBREADLINE */
683     char buf[LINE_LEN];
684 #endif                          /* HAVE_LIBREADLINE */
685
686     if (!existing_connection && (fd = connect_fcron()) == ERR)
687         return ERR;
688
689 #ifdef HAVE_LIBREADLINE
690     /* Allow conditional parsing of the ~/.inputrc file. */
691     rl_readline_name = "fcrondyn";
692
693     /* Tell the completer that we want a crack first. */
694     rl_attempted_completion_function = rl_cmpl_fcrondyn;
695
696     while (1) {
697         line_read = readline("fcrondyn> ");
698
699         if (line_read == NULL) {
700             /* Handle EOF gracefully and move past the prompt line */
701             printf("\n");
702             break;
703         }
704
705         return_code = talk_fcron(line_read, fd);
706
707 #ifdef HAVE_READLINE_HISTORY
708         if (line_read && *line_read) {
709             add_history(line_read);
710         }
711 #endif                          /* HAVE_READLINE_HISTORY */
712
713         free(line_read);
714         if (return_code == QUIT_CMD || return_code == ERR) {
715             break;
716         }
717     }
718 #else                           /* HAVE_LIBREADLINE */
719     while (fprintf(stderr, "fcrondyn> ")
720            && fgets(buf, sizeof(buf), stdin) != NULL
721            && (return_code = talk_fcron(buf, fd)) != QUIT_CMD
722            && return_code != ERR) ;
723 #endif                          /* HAVE_LIBREADLINE */
724
725     if (!existing_connection)
726         xclose_check(&fd, "unix socket");
727
728     return OK;
729 }
730
731
732 void
733 parseopt(int argc, char *argv[])
734   /* set options */
735 {
736
737     int c, i;
738     extern char *optarg;
739     extern int optind, opterr, optopt;
740
741     /* constants and variables defined by command line */
742
743     while (1) {
744         c = getopt(argc, argv, "hVdc:ix:");
745         if (c == EOF)
746             break;
747         switch (c) {
748
749         case 'V':
750             info();
751             break;
752
753         case 'h':
754             usage();
755             break;
756
757         case 'd':
758             debug_opt = 1;
759             break;
760
761         case 'c':
762             Set(fcronconf, optarg);
763             break;
764
765         case 'i':
766             Free_safe(cmd_str);
767             break;
768
769         case 'x':
770             Set(cmd_str, optarg);
771             break;
772
773         case ':':
774             fprintf(stderr, "(setopt) Missing parameter.\n");
775             usage();
776
777         case '?':
778             usage();
779
780         default:
781             fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
782         }
783
784     }
785
786     if (optind < argc) {
787         for (i = optind; i <= argc; i++)
788             fprintf(stderr, "Unknown argument \"%s\"", argv[i]);
789         usage();
790     }
791 }
792
793
794 int
795 main(int argc, char **argv)
796 {
797     int return_code = 0;
798     int fd = (-1);              /* fd == -1 means connection to fcron is not currently open */
799     struct passwd *pass = NULL;
800
801     rootuid = get_user_uid_safe(ROOTNAME);
802     rootgid = get_group_gid_safe(ROOTGROUP);
803
804     if (strrchr(argv[0], '/') == NULL)
805         prog_name = argv[0];
806     else
807         prog_name = strrchr(argv[0], '/') + 1;
808
809     user_uid = getuid();
810     user_gid = getgid();
811     if ((pass = getpwuid(user_uid)) == NULL)
812         die("user \"%s\" is not in passwd file. Aborting.", USERNAME);
813     user_str = strdup2(pass->pw_name);
814
815     /* drop suid rights that we don't need, but keep the sgid rights
816      * for now as we will need them for read_conf() and is_allowed() */
817 #ifdef USE_SETE_ID
818     seteuid_safe(user_uid);
819 #endif
820     if (setuid(user_uid) < 0)
821         die_e("could not setuid() to %d", user_uid);
822
823     /* interpret command line options */
824     parseopt(argc, argv);
825
826     /* read fcron.conf and update global parameters */
827     read_conf();
828
829     if (!is_allowed(user_str)) {
830         die("User \"%s\" is not allowed to use %s. Aborting.", user_str,
831             prog_name);
832     }
833
834     /* we don't need anymore special rights : drop remaining ones */
835 #ifdef USE_SETE_ID
836     setegid_safe(user_gid);
837 #endif
838     if (setgid(user_gid) < 0)
839         die_e("could not setgid() to %d", user_gid);
840
841     /* check for broken pipes ... */
842     signal(SIGPIPE, sigpipe_handler);
843
844     if (cmd_str == NULL)
845         return_code = interactive_mode(fd);
846     else
847         return_code = talk_fcron(cmd_str, fd);
848
849     xexit((return_code == OK) ? EXIT_OK : EXIT_ERR);
850
851     /* never reached */
852     return EXIT_OK;
853
854 }