]> granicus.if.org Git - fcron/blob - fcrondyn.c
Updated copyright years to 2013
[fcron] / fcrondyn.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2013 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             "  -c f       make fcrontab use config file f.\n"
141             "  -d         set up debug mode.\n"
142             "  -h         display this help message.\n"
143             "  -V         display version & infos about fcrondyn.\n" "\n");
144
145     exit(EXIT_ERR);
146 }
147
148 RETSIGTYPE
149 sigpipe_handler(int x)
150     /* handle broken pipes ... */
151 {
152     fprintf(stderr,
153             "Broken pipe : fcron may have closed the connection\nThe connection "
154             "has been idle for more than %ds, or fcron may not be running anymore.\n",
155             MAX_IDLE_TIME);
156     fprintf(stderr, "Exiting ...\n");
157
158     xexit(EXIT_ERR);
159 }
160
161 void
162 xexit(int exit_val)
163     /* clean & exit */
164 {
165     Free_safe(cmd_str);
166
167     exit(exit_val);
168 }
169
170
171 /* used in parse_cmd : */
172 #define Write_cmd(DATA) \
173           memcpy(buf + *cmd_len, &DATA, sizeof(long int)); \
174           *cmd_len += 1;
175
176 #define Strncmp(STR1, STR2, STR1_SIZE) \
177           strncmp(STR1, STR2, (STR1_SIZE < strlen(STR2)) ? strlen(STR2) : STR1_SIZE)
178
179 int
180 parse_cmd(char *cmd_str, long int **cmd, int *cmd_len)
181     /* read a command string, check if it is valid and translate it */
182 {
183     long int buf[SOCKET_MSG_LEN];
184     int word_size = 0;
185     int i = 0, j = 0, rank = -1;
186     long int int_buf = 0;
187     struct passwd *pass = NULL;
188 #ifdef SYSFCRONTAB
189     long int sysfcrontab_uid = SYSFCRONTAB_UID;
190 #endif
191
192     bzero(buf, sizeof(buf));
193     *cmd_len = 0;
194     remove_blanks(cmd_str);     /* at the end of the string */
195
196     if ((word_size = get_word(&cmd_str)) == 0) {
197         fprintf(stderr, "Warning : Zero-length command name : line ignored.\n");
198         return ZEROLEN_CMD;
199     }
200
201     for (i = 0; i < cmd_list_len; i++) {
202         int j;
203         if (Strncmp(cmd_str, cmd_list[i].cmd_name, word_size) == 0) {
204             rank = i;
205             break;
206         }
207         for (j = 0; j < MAX_NUM_ALIAS && cmd_list[i].cmd_alias[j] != NULL; j++) {
208             if (Strncmp(cmd_str, cmd_list[i].cmd_alias[j], word_size) == 0) {
209                 rank = i;
210                 break;
211             }
212         }
213     }
214     if (rank == (-1)) {
215         fprintf(stderr, "Error : Unknown command.\n");
216         return CMD_NOT_FOUND;
217     }
218     else if (cmd_list[rank].cmd_code == QUIT_CMD) {
219         if (debug_opt)
220             fprintf(stderr, "quit command\n");
221         return QUIT_CMD;
222     }
223     else if (cmd_list[rank].cmd_code == HELP_CMD) {
224         if (debug_opt)
225             fprintf(stderr, "Help command\n");
226         return HELP_CMD;
227     }
228
229     Write_cmd(cmd_list[rank].cmd_code);
230
231     if (debug_opt)
232         fprintf(stderr, "command : %s\n", cmd_list[i].cmd_name);
233
234     cmd_str += word_size;
235     for (i = 0; i < cmd_list[rank].cmd_numopt; i++) {
236
237         if ((word_size = get_word(&cmd_str)) == 0) {
238
239             if (cmd_list[rank].cmd_default[i] == ARG_REQUIRED) {
240                 fprintf(stderr, "Error : arg required !\n");
241                 return INVALID_ARG;
242             }
243
244             /* use default value : currently, works only with CUR_USER */
245             if (user_uid == rootuid) {
246                 /* default for root = all */
247                 int_buf = ALL;
248                 Write_cmd(int_buf);
249                 if (debug_opt)
250                     fprintf(stderr, "  uid = ALL\n");
251             }
252             else {
253                 Write_cmd(user_uid);
254                 if (debug_opt)
255                     fprintf(stderr, "  uid = %d\n", (int)user_uid);
256             }
257
258         }
259
260         else {
261
262             /* get value from line ... */
263             switch (cmd_list[rank].cmd_opt[i]) {
264
265             case USER:
266                 int_buf = (long int)*(cmd_str + word_size);
267                 *(cmd_str + word_size) = '\0';
268 #ifdef SYSFCRONTAB
269                 if (strcmp(cmd_str, SYSFCRONTAB) == 0) {
270                     Write_cmd(sysfcrontab_uid);
271                 }
272                 else {
273 #endif
274                     if ((pass = getpwnam(cmd_str)) == NULL) {
275                         fprintf(stderr,
276                                 "Error : '%s' isn't a valid username.\n",
277                                 cmd_str);
278                         return INVALID_ARG;
279                     }
280                     Write_cmd(pass->pw_uid);
281 #ifdef SYSFCRONTAB
282                 }
283 #endif
284                 *(cmd_str + word_size) = (char)int_buf;
285                 cmd_str += word_size;
286                 if (debug_opt)
287                     fprintf(stderr, "  uid = %d\n",
288 #ifdef SYSFCRONTAB
289                             (pass) ? (int)pass->pw_uid : (int)SYSFCRONTAB_UID
290 #else
291                             (int)pass->pw_uid
292 #endif
293                         );
294                 break;
295
296             case JOBID:
297                 /* after strtol(), cmd_str will be updated (first non-number char) */
298                 if ((int_buf = strtol(cmd_str, &cmd_str, 10)) < 0
299                     || int_buf >= LONG_MAX || (!isspace((int)*cmd_str)
300                                                && *cmd_str != '\0')) {
301                     fprintf(stderr, "Error : invalid jobid.\n");
302                     return INVALID_ARG;
303                 }
304                 Write_cmd(int_buf);
305                 if (debug_opt)
306                     fprintf(stderr, "  jobid = %ld\n", int_buf);
307                 break;
308
309             case TIME_AND_DATE:
310                 /* argghh !!! no standard function ! */
311                 break;
312
313             case NICE_VALUE:
314                 /* after strtol(), cmd_str will be updated (first non-number char) */
315                 if ((int_buf = strtol(cmd_str, &cmd_str, 10)) > 20
316                     || (int_buf < 0 && getuid() != rootuid) || int_buf < -20
317                     || (!isspace((int)*cmd_str) && *cmd_str != '\0')) {
318                     fprintf(stderr, "Error : invalid nice value.\n");
319                     return INVALID_ARG;
320                 }
321                 Write_cmd(int_buf);
322                 if (debug_opt)
323                     fprintf(stderr, "  nicevalue = %ld\n", int_buf);
324                 break;
325
326             case SIGNAL:
327                 if (isalpha((int)*cmd_str)) {
328                     for (j = 0; j < word_size; j++)
329                         *(cmd_str + j) = tolower(*(cmd_str + j));
330                     if (Strncmp(cmd_str, "hup", word_size) == 0)
331                         int_buf = SIGHUP;
332                     else if (Strncmp(cmd_str, "int", word_size) == 0)
333                         int_buf = SIGINT;
334                     else if (Strncmp(cmd_str, "quit", word_size) == 0)
335                         int_buf = SIGQUIT;
336                     else if (Strncmp(cmd_str, "kill", word_size) == 0)
337                         int_buf = SIGKILL;
338                     else if (Strncmp(cmd_str, "alrm", word_size) == 0)
339                         int_buf = SIGALRM;
340                     else if (Strncmp(cmd_str, "term", word_size) == 0)
341                         int_buf = SIGTERM;
342                     else if (Strncmp(cmd_str, "usr1", word_size) == 0)
343                         int_buf = SIGUSR1;
344                     else if (Strncmp(cmd_str, "usr2", word_size) == 0)
345                         int_buf = SIGUSR2;
346                     else if (Strncmp(cmd_str, "cont", word_size) == 0)
347                         int_buf = SIGCONT;
348                     else if (Strncmp(cmd_str, "stop", word_size) == 0)
349                         int_buf = SIGSTOP;
350                     else if (Strncmp(cmd_str, "tstp", word_size) == 0)
351                         int_buf = SIGTSTP;
352                     else {
353                         fprintf(stderr,
354                                 "Error : unknow signal (try integer value)\n");
355                         return INVALID_ARG;
356                     }
357                     cmd_str += word_size;
358                 }
359                 /* after strtol(), cmd_str will be updated (first non-number char) */
360                 else if ((int_buf = strtol(cmd_str, &cmd_str, 10)) <= 0
361                          || int_buf >= LONG_MAX || (!isspace((int)*cmd_str)
362                                                     && *cmd_str != '\0')) {
363                     fprintf(stderr, "Error : invalid signal value.\n");
364                     return INVALID_ARG;
365                 }
366                 Write_cmd(int_buf);
367                 if (debug_opt)
368                     fprintf(stderr, "  signal = %ld\n", int_buf);
369                 break;
370
371             default:
372                 fprintf(stderr, "Error : Unknown arg !");
373                 return INVALID_ARG;
374             }
375         }
376     }
377
378     Skip_blanks(cmd_str);
379     if (*cmd_str != '\0')
380         fprintf(stderr, "Warning : too much arguments : '%s' ignored.\n",
381                 cmd_str);
382
383     /* This is a valid command ... */
384     *cmd = alloc_safe(*cmd_len * sizeof(long int), "command string");
385     memcpy(*cmd, buf, *cmd_len * sizeof(long int));
386
387     return OK;
388 }
389
390 int
391 authenticate_user_password(int fd)
392     /* authenticate user */
393 {
394     char *password = NULL;
395     char buf[USER_NAME_LEN + 16];
396     int len = 0;
397     fd_set read_set;            /* needed to use select to check if some data is waiting */
398     struct timeval tv;
399
400     snprintf(buf, sizeof(buf), "password for %s :", user_str);
401     if ((password = read_string(CONV_ECHO_OFF, buf)) == NULL)
402         return ERR;
403
404     len = snprintf(buf, sizeof(buf), "%s", user_str) + 1;
405     len += snprintf(buf + len, sizeof(buf) - len, "%s", password) + 1;
406     send(fd, buf, len, 0);
407     Overwrite(buf);
408     Overwrite(password);
409     Free_safe(password);
410
411     tv.tv_sec = MAX_WAIT_TIME;
412     tv.tv_usec = 0;
413     FD_ZERO(&read_set);
414     FD_SET(fd, &read_set);
415     if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) {
416         error_e("Couldn't get data from socket during %d seconds.",
417                 MAX_WAIT_TIME);
418         return ERR;
419     }
420     while (recv(fd, buf, sizeof(buf), 0) < 0 && errno == EINTR)
421         if (errno == EINTR && debug_opt)
422             fprintf(stderr, "Got EINTR ...");
423     if (strncmp(buf, "1", sizeof("1")) != 0)
424         return ERR;
425
426     return OK;
427 }
428
429
430 int
431 connect_fcron(void)
432     /* connect to fcron through a socket, and identify user */
433 {
434     int fd = -1;
435     struct sockaddr_un addr;
436     int len = 0;
437     int sun_len = 0;
438
439     if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
440         die_e("could not create socket");
441
442     addr.sun_family = AF_UNIX;
443     len = strlen(fifofile);
444     if (len > sizeof(addr.sun_path) - 1)
445         die("Error : fifo file path too long (max is %d)",
446             sizeof(addr.sun_path) - 1);
447     /* length(fifofile) < sizeof(add.sun_path), so strncpy will terminate
448      * the string with at least one \0 (not necessarily required by the OS,
449      * but still a good idea) */
450     strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path));
451     addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
452     sun_len = (addr.sun_path - (char *)&addr) + len;
453 #if HAVE_SA_LEN
454     addr.sun_len = sun_len;
455 #endif
456
457     if (connect(fd, (struct sockaddr *)&addr, sun_len) < 0)
458         die_e("Cannot connect() to fcron (check if fcron is running)");
459
460 /* Nothing to do on the client side if we use SO_PASSCRED */
461 #ifndef SO_PASSCRED
462     if (authenticate_user_password(fd) == ERR) {
463         fprintf(stderr, "Invalid password or too many authentication failures"
464                 " (try to connect later).\n(In the later case, fcron rejects all"
465                 " new authentication during %d secs)\n", AUTH_WAIT);
466         die("Unable to authenticate user");
467     }
468 #endif                          /* SO_PASSCRED */
469
470     return fd;
471
472 }
473
474 int
475 talk_fcron(char *cmd_str, int fd)
476     /* read a string command, check if it is valid and translate it,
477      * send it to fcron, and print its answer */
478 {
479     long int *cmd = NULL;
480     int cmd_len = 0;
481     char buf[LINE_LEN];
482     size_t read_len = 0;
483     char existing_connection = (fd < 0) ? 0 : 1;
484     fd_set read_set;            /* needed to use select to check if some data is waiting */
485     struct timeval tv;
486
487     switch (parse_cmd(cmd_str, &cmd, &cmd_len)) {
488     case OK:
489         break;
490     case HELP_CMD:
491         {
492             int i, j, len;
493             printf("Command recognized by fcrondyn :\n");
494             printf("------------------------------\n");
495             for (i = 0; i < cmd_list_len; i++) {
496                 len = printf("%s ", cmd_list[i].cmd_name);
497
498                 /* print args : */
499                 for (j = 0; j < cmd_list[i].cmd_numopt; j++) {
500                     if (cmd_list[i].cmd_default[j] != ARG_REQUIRED)
501                         len += printf("[");
502                     switch (cmd_list[i].cmd_opt[j]) {
503                     case USER:
504                         len += printf("user");
505                         break;
506                     case JOBID:
507                         len += printf("jobid");
508                         break;
509                     case TIME_AND_DATE:
510                         len += printf("time");
511                         break;
512                     case NICE_VALUE:
513                         len += printf("niceval");
514                         break;
515                     case SIGNAL:
516                         len += printf("sig");
517                         break;
518                     case BOOLEAN:
519                         len += printf("bool");
520                         break;
521                     default:
522                         len += printf("unknown_arg!");
523                     }
524                     if (cmd_list[i].cmd_default[j] != ARG_REQUIRED)
525                         len += printf("]");
526                     len += printf(" ");
527                 }
528                 /* Align correctly the descriptions : */
529                 printf("%*s%s", 24 - len, "", cmd_list[i].cmd_desc);
530
531                 /* print alias list (if any) */
532                 if (cmd_list[i].cmd_alias[0] != NULL) {
533                     printf(" (aliases:");
534                     for (j = 0;
535                          j < MAX_NUM_ALIAS && cmd_list[i].cmd_alias[j] != NULL;
536                          j++) {
537                         printf(" %s", cmd_list[i].cmd_alias[j]);
538                     }
539                     printf(")");
540                 }
541
542                 printf("\n");
543             }
544         }
545         return HELP_CMD;
546     case QUIT_CMD:
547         return QUIT_CMD;
548     case CMD_NOT_FOUND:
549         return CMD_NOT_FOUND;
550     case INVALID_ARG:
551         return INVALID_ARG;
552     case ZEROLEN_CMD:
553         return ZEROLEN_CMD;
554     default:
555         return ERR;
556     }
557
558     /* This is a valid command (so we'll have to free() it) ... */
559
560     if (!existing_connection && (fd = connect_fcron()) == ERR)
561         return ERR;
562
563     send(fd, cmd, cmd_len * sizeof(long int), 0);
564     Free_safe(cmd);
565     cmd_len = 0;
566
567     tv.tv_sec = MAX_WAIT_TIME;
568     tv.tv_usec = 0;
569     FD_ZERO(&read_set);
570     FD_SET(fd, &read_set);
571     if (select(fd + 1, &read_set, NULL, NULL, &tv) <= 0) {
572         error_e("Couldn't get data from socket during %d seconds.",
573                 MAX_WAIT_TIME);
574         return ERR;
575     }
576
577     while ((read_len = (size_t) recv(fd, buf, sizeof(buf), 0)) >= 0
578            || errno == EINTR) {
579         if (errno == EINTR && debug_opt)
580             fprintf(stderr, "got EINTR ...\n");
581         else if (read_len > sizeof(buf)) {
582             /* weird ... no data yet ? */
583             if (debug_opt)
584                 fprintf(stderr, "no data yet ?");
585         }
586         else if (read_len <= 0) {
587             if (debug_opt)
588                 fprintf(stderr, "read_len = %d\n", (int)read_len);
589             fprintf(stderr, "connection closed by fcron\n");
590             shutdown(fd, SHUT_RDWR);
591             return ERR;
592         }
593         else {
594             if (write(STDOUT_FILENO, buf, read_len) < 0)
595                 error_e("unable to write() to STDOUT_FILENO");
596             if (read_len >= sizeof(END_STR) &&
597                 strncmp(&buf[read_len - sizeof(END_STR)], END_STR,
598                         sizeof(END_STR)) == 0)
599                 break;
600         }
601     }
602     if (read_len < 0)
603         error_e("error in recv()");
604
605     if (!existing_connection)
606         xclose_check(&fd, "unix socket");
607
608     return OK;
609 }
610
611 #ifdef HAVE_LIBREADLINE
612 /* Attempt to complete on the contents of TEXT. START and END bound the
613    region of rl_line_buffer that contains the word to complete. TEXT is
614    the word to complete. We can use the entire contents of rl_line_buffer
615    in case we want to do some simple parsing. Return the array of matches,
616    or NULL if there aren't any. */
617 char **
618 rl_cmpl_fcrondyn(const char *text, int start, int end)
619 {
620     char **matches;
621
622     matches = (char **)NULL;
623
624     /* If this word is at the start of the line, then it is a command
625      * to complete. Otherwise it is an argument which we ignore for now */
626     if (start == 0) {
627         matches = rl_completion_matches(text, rl_cmpl_command_generator);
628     }
629
630     return (matches);
631 }
632
633 /* Generator function for command completion. STATE lets us know whether
634    to start from scratch; without any state (i.e. STATE == 0), then we
635    start at the top of the list. */
636 char *
637 rl_cmpl_command_generator(const char *text, int state)
638 {
639     static int list_index, len;
640     char *name = NULL;
641
642     /* If this is a new word to complete, initialize now.  This includes
643      * saving the length of TEXT for efficiency, and initializing the index
644      * variable to 0. */
645     if (!state) {
646         list_index = 0;
647         len = strlen(text);
648     }
649
650     /* Return the next name which partially matches from the command list. */
651     while (list_index < cmd_list_len) {
652         name = cmd_list[list_index].cmd_name;
653         list_index++;
654         if (strncmp(name, text, len) == 0) {
655             return (strdup2(name));
656         }
657     }
658
659     /* If no names matched, then return NULL. */
660     return ((char *)NULL);
661 }
662 #endif                          /* HAVE_LIBREADLINE */
663
664 int
665 interactive_mode(int fd)
666     /* provide user a prompt, read command, send it to fcron, print its answer,
667      * then give another prompt, etc, until user type an exit command */
668 {
669     char existing_connection = (fd < 0) ? 0 : 1;
670     int return_code = 0;
671 #ifdef HAVE_LIBREADLINE
672     char *line_read = NULL;
673 #else                           /* HAVE_LIBREADLINE */
674     char buf[LINE_LEN];
675 #endif                          /* HAVE_LIBREADLINE */
676
677     if (!existing_connection && (fd = connect_fcron()) == ERR)
678         return ERR;
679
680 #ifdef HAVE_LIBREADLINE
681     /* Allow conditional parsing of the ~/.inputrc file. */
682     rl_readline_name = "fcrondyn";
683
684     /* Tell the completer that we want a crack first. */
685     rl_attempted_completion_function = rl_cmpl_fcrondyn;
686
687     while (1) {
688         line_read = readline("fcrondyn> ");
689         return_code = talk_fcron(line_read, fd);
690
691 #ifdef HAVE_READLINE_HISTORY
692         if (line_read && *line_read) {
693             add_history(line_read);
694         }
695 #endif                          /* HAVE_READLINE_HISTORY */
696
697         free(line_read);
698         if (return_code == QUIT_CMD || return_code == ERR) {
699             break;
700         }
701     }
702 #else                           /* HAVE_LIBREADLINE */
703     while (fprintf(stderr, "fcrondyn> ")
704            && fgets(buf, sizeof(buf), stdin) != NULL
705            && (return_code = talk_fcron(buf, fd)) != QUIT_CMD
706            && return_code != ERR) ;
707 #endif                          /* HAVE_LIBREADLINE */
708
709     if (!existing_connection)
710         xclose_check(&fd, "unix socket");
711
712     return OK;
713 }
714
715
716 void
717 parseopt(int argc, char *argv[])
718   /* set options */
719 {
720
721     int c, i;
722     extern char *optarg;
723     extern int optind, opterr, optopt;
724
725     /* constants and variables defined by command line */
726
727     while (1) {
728         c = getopt(argc, argv, "hVdc:ix:");
729         if (c == EOF)
730             break;
731         switch (c) {
732
733         case 'V':
734             info();
735             break;
736
737         case 'h':
738             usage();
739             break;
740
741         case 'd':
742             debug_opt = 1;
743             break;
744
745         case 'c':
746             Set(fcronconf, optarg);
747             break;
748
749         case 'i':
750             Free_safe(cmd_str);
751             break;
752
753         case 'x':
754             Set(cmd_str, optarg);
755             break;
756
757         case ':':
758             fprintf(stderr, "(setopt) Missing parameter.\n");
759             usage();
760
761         case '?':
762             usage();
763
764         default:
765             fprintf(stderr, "(setopt) Warning: getopt returned %c.\n", c);
766         }
767
768     }
769
770     if (optind < argc) {
771         for (i = optind; i <= argc; i++)
772             fprintf(stderr, "Unknown argument \"%s\"", argv[i]);
773         usage();
774     }
775 }
776
777
778 int
779 main(int argc, char **argv)
780 {
781     int return_code = 0;
782     int fd = (-1);              /* fd == -1 means connection to fcron is not currently open */
783     struct passwd *pass = NULL;
784
785     rootuid = get_user_uid_safe(ROOTNAME);
786     rootgid = get_group_gid_safe(ROOTGROUP);
787
788     if (strrchr(argv[0], '/') == NULL)
789         prog_name = argv[0];
790     else
791         prog_name = strrchr(argv[0], '/') + 1;
792
793     user_uid = getuid();
794     user_gid = getgid();
795     if ((pass = getpwuid(user_uid)) == NULL)
796         die("user \"%s\" is not in passwd file. Aborting.", USERNAME);
797     user_str = strdup2(pass->pw_name);
798
799     /* drop suid rights that we don't need, but keep the sgid rights
800      * for now as we will need them for read_conf() and is_allowed() */
801 #ifdef USE_SETE_ID
802     seteuid_safe(user_uid);
803 #endif
804     if (setuid(user_uid) < 0)
805         die_e("could not setuid() to %d", user_uid);
806
807     /* interpret command line options */
808     parseopt(argc, argv);
809
810     /* read fcron.conf and update global parameters */
811     read_conf();
812
813     if (!is_allowed(user_str)) {
814         die("User \"%s\" is not allowed to use %s. Aborting.", user_str,
815             prog_name);
816     }
817
818     /* we don't need anymore special rights : drop remaining ones */
819 #ifdef USE_SETE_ID
820     setegid_safe(user_gid);
821 #endif
822     if (setgid(user_gid) < 0)
823         die_e("could not setgid() to %d", user_gid);
824
825     /* check for broken pipes ... */
826     signal(SIGPIPE, sigpipe_handler);
827
828     if (cmd_str == NULL)
829         return_code = interactive_mode(fd);
830     else
831         return_code = talk_fcron(cmd_str, fd);
832
833     xexit((return_code == OK) ? EXIT_OK : EXIT_ERR);
834
835     /* never reached */
836     return EXIT_OK;
837
838 }