Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtcm / server / svcmain.c
1 /* $TOG: svcmain.c /main/10 1998/04/06 13:13:49 mgreess $ */
2 /*
3  *  (c) Copyright 1993, 1994 Hewlett-Packard Company
4  *  (c) Copyright 1993, 1994 International Business Machines Corp.
5  *  (c) Copyright 1993, 1994 Novell, Inc.
6  *  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
7  */
8
9 #include <EUSCompat.h>
10 #include <stdio.h>
11 #include <errno.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <sys/time.h>
16 #include <sys/resource.h>
17 #ifdef SVR4
18 #ifndef _NETINET_IN_H
19 #include <netinet/in.h>
20 #endif /* _NETINET_IN_H */
21 #endif
22
23 #if defined(SunOS) || defined(USL) || defined(__uxp__)
24 #include <netconfig.h>
25 #include <netdir.h>
26 #include <sys/stropts.h>
27 #include <tiuser.h>
28 #endif /* SunOS || USL || __uxp__ */
29 #include <sys/param.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <rpc/rpc.h>
33 #include <sys/file.h>
34 #include <sys/signal.h>
35 #include <pwd.h>
36 #include <grp.h>
37 #include "rpcextras.h"
38 #include "log.h"
39 #include "cmscalendar.h"
40 #include "repeat.h"
41 #include "lutil.h"
42 #include "cmsdata.h"
43
44 #ifndef S_IRWXU
45 #define S_IRWXU         (S_IRUSR|S_IWUSR|S_IXUSR)
46 #endif
47 #ifndef S_IRWXG
48 #define S_IRWXG         (S_IRGRP|S_IWGRP|S_IXGRP)
49 #endif
50 #ifndef S_IRWXO
51 #define S_IRWXO         (S_IROTH|S_IWOTH|S_IXOTH)
52 #endif
53
54 #define S_MASK  (S_INPUT|S_HIPRI|S_ERROR|S_HANGUP)
55
56 int debug;
57 static int standalone;                  /* default is 0 */
58 static int received_sighup = 0;         /* 1 means we get SIGHUP */
59 static int rpc_in_process = 0;          /* 1 means processing client request */
60 static int garbage_collection_time = 240; /* in min; default time is 4:00am */
61
62 char    *pgname;
63
64 uid_t daemon_uid;
65 gid_t daemon_gid;
66
67 /*
68  * get garbage collection time
69  * the given string should be in the format hhmm
70  * where hh is 0 - 23 and mm is 00 - 59
71  */
72 static int
73 _GetGtime(char *timestr)
74 {
75         int     hour, minute, len, i;
76
77         if (timestr == NULL)
78                 goto error;
79
80         if ((len = strlen(timestr)) > 4)
81                 goto error;
82
83         for (i = 0; i < len; i++) {
84                 if (timestr[i] < '0' || timestr[i] > '9')
85                         goto error;
86         }
87
88         minute = atoi(&timestr[len - 2]);
89         timestr[len - 2] = NULL;
90         hour = atoi(timestr);
91
92         if (hour > 23 || minute > 59)
93                 goto error;
94
95         garbage_collection_time = hour * 60 + minute;
96
97         return (0);
98
99 error:
100         fprintf(stderr, "The time specified is invalid.\n");
101         return (-1);
102 }
103
104 static void
105 parse_args(int argc, char **argv)
106 {
107         int     opt;
108
109         if (pgname = strrchr (argv[0], '/'))
110                 pgname++;
111         else
112                 pgname = argv[0];
113
114         while ((opt = getopt (argc, argv, "dsg:")) != -1)
115         {
116                 switch (opt)
117                 {
118                 case 'd':
119                         debug = 1;
120                         break;
121                 case 's':
122                         standalone = 1;
123                         break;
124                 case 'g':
125                         if (_GetGtime(optarg))
126                                 goto error;
127                         break;
128                 case '?':
129                         goto error;
130                 }
131         }
132
133         if (optind == argc)
134                 return;
135
136 #if defined(_aix)
137         /*
138          * rpc.cmsd gets started by the inetd.
139          * On AIX inetd requires that two arguments be supplied to the RPC
140          * programs as follows (from the inetd.conf man page):
141          *
142          * ServerArgs      Specifies the command line arguments that the
143          * inetd daemon should use to execute the server. The maximum number
144          * of arguments is five. The first argument specifies the name of the
145          * server used.  If the SocketType parameter is sunrpc_tcp or
146          * sunrpc_udp, * the second argument specifies the program name and
147          * the third argument specifies the version of the program. For
148          * services that the inetd daemon provides internally, this field
149          * should be empty.
150          */
151
152         else if (optind == 1 && argc >= 3)
153         {
154                 int i,j;
155                 char **argv_r;
156                 
157                 if (argc == 3)
158                   return;
159                   
160                 argv_r = (char **) malloc(argc * sizeof(char *));
161
162                 argv_r[0] = argv[0];
163                 for (i=optind+2, j=1; i<argc; i++,j++)
164                   argv_r[j] = argv[i];
165                 parse_args(argc-2, argv_r);
166
167                 free((void *) argv_r);
168                 return;
169         }
170 #endif
171                 
172 error:
173         fprintf (stderr, "Usage: %s [-d] [-s] [-g hhmm]\n", pgname);
174         exit (-1);
175 }
176
177 static void
178 init_dir()
179 {
180         char *dir = _DtCMS_DEFAULT_DIR;
181         char msgbuf[BUFSIZ];
182         int create_dir;
183         struct stat info;
184         mode_t mode;
185
186         if (geteuid() != 0)
187         {
188                 fprintf (stderr,
189                         "%s: must be run in super-user mode!  Exited.\n",
190                         pgname);
191                 exit (-1);
192         }
193
194         create_dir = 0;
195         if (stat(dir, &info))
196         {
197                 /* if directory does not exist, create the directory */
198                 if ((errno != ENOENT) || mkdir(dir, S_IRWXU|S_IRWXG|S_IRWXO))
199                 {
200                         if (errno == ENOENT)
201                                 sprintf(msgbuf, "%s: cannot create %s.\n%s: %s",
202                                         pgname, dir, pgname, "System error");
203                         else
204                                 sprintf(msgbuf, "%s: cannot access %s.\n%s: %s",
205                                         pgname, dir, pgname, "System error");
206                         perror (msgbuf);
207                         exit (-1);
208                 }
209                 create_dir = 1;
210         }
211
212         /* if dir is just created, we need to do chmod and chown.
213          * Otherwise, only do chmod and/or chown if permssion and/or
214          * ownership is wrong.
215          */
216         mode = S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
217
218         if (create_dir || info.st_mode != (mode | S_IFDIR)) {
219
220                 /* set directory permission to be "rwxrwsrwt" */
221                 if (chmod(dir, mode)) {
222                         sprintf(msgbuf, "%s: Permission on %s%s\n%s%s\n%s%s",
223                                 pgname, dir,
224                                 " is wrong but cannot be corrected.", pgname,
225                                 ": This might happen if you are mounting the directory.",
226                                 pgname, ": System error");
227                         perror(msgbuf);
228                         if (create_dir)
229                                 rmdir(dir);
230                         exit(-1);
231                 }
232         }
233
234         if (create_dir || info.st_uid!=daemon_uid || info.st_gid!=daemon_gid) {
235                 /* set directory ownership to: owner = 1, group = 1 */
236                 if (chown(dir, daemon_uid, daemon_gid)) {
237                         sprintf(msgbuf, "%s: Ownership on %s%s\n%s%s\n%s%s",
238                                 pgname, dir,
239                                 " is wrong but cannot be corrected.", pgname,
240                                 ": This might happen if you are mounting the directory.",
241                                 pgname, ": System error");
242                         perror(msgbuf);
243                         if (create_dir)
244                                 rmdir(dir);
245                         exit(-1);
246                 }
247         }
248
249         /* Change current directory, so core file can be dumped. */
250         chdir (dir);
251 }
252
253 /*
254  * send a SIGHUP signal to the rpc.cmsd that is already running
255  */
256 static void
257 send_hup()
258 {
259         FILE    *fp = NULL;
260         char    buf[BUFSIZ];
261         pid_t   pid, mypid = getpid();
262         extern FILE *popen(const char *, const char *);
263         extern int pclose(FILE *);
264
265         sprintf(buf, "ps -e|grep rpc.cmsd|grep -v grep");
266
267         if ((fp = popen(buf, "r")) == NULL) {
268                 if (debug)
269                         fprintf(stderr, "rpc.cmsd: popen failed\n");
270         } else {
271                 while (fgets(buf, sizeof(buf), fp) != NULL) {
272                         if ((pid = atol(buf)) != mypid) {
273                                 if (kill(pid, SIGHUP))
274                                     perror("rpc.cmsd: failed to send SIGHUP");
275                                 if (debug)
276                                     fprintf(stderr, "rpc.cmsd: %s %ld\n",
277                                                 "sent SIGHUP to", (long)pid);
278                         }
279                 }
280                 pclose(fp);
281         }
282 }
283
284 /*
285  * We only allow one rpc.cmsd to run on each machine.
286  */
287 static int
288 lock_it()
289 {
290         char *dir = _DtCMS_DEFAULT_DIR;
291         char    buff [MAXPATHLEN];
292         int     error;
293         int     fd;
294 #ifdef SVR4
295         struct flock locker;
296         locker.l_type = F_WRLCK;
297         locker.l_whence = 0;
298         locker.l_start = 0;
299         locker.l_len = 0;
300 #endif /* SVR4 */
301
302         strcpy (buff, dir);
303         strcat (buff, "/.lock.");
304
305         /* 
306          * /var/spool might be mounted.  Use .lock.hostname to
307          * prevent more than one cms running in each host.
308          */
309         strcat(buff, _DtCmGetLocalHost());
310
311         fd = open(buff, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
312         if (fd < 0)
313         {
314                 perror (buff);
315                 exit (-1);
316         }
317
318         /*
319          * Note, we have to use flock() instead of lockf() because cms process
320          * is run in each host.
321          */
322 #ifdef SVR4
323         if (fcntl (fd, F_SETLK, &locker) != 0)
324 #else
325         if (flock (fd, LOCK_EX|LOCK_NB) != 0)
326 #endif /* SVR4 */
327         {
328                 error = errno;
329
330                 close(fd);
331
332                 if (error != EWOULDBLOCK && error != EACCES) {
333
334                         perror ("rpc.cmsd: failed to lock lockfile");
335                         fprintf(stderr, "error = %d\n", error);
336                         exit (-1);
337
338                 } else {
339                         if (debug)
340                                 fprintf(stderr, "rpc.cmsd: %s\n",
341                                     "lock_it failed due to another process");
342                         
343                         /* cms has been running.... */
344                         return(error);
345                 }
346         }
347
348         return (0);
349 }
350
351 static void
352 program(struct svc_req *rqstp, register SVCXPRT *transp)
353 {
354         char *result;
355         char *argument = NULL;
356         program_handle ph = getph();
357         struct rpcgen_table *proc;
358
359         /* set rpc_in_process so that sighup handler won't exit right away */
360         rpc_in_process = 1;
361  
362         /* first do some bounds checking: */
363         if (rqstp->rq_vers >= ph->nvers) {
364                 svcerr_noproc(transp);
365                 goto done;
366         }
367         if (ph->prog[rqstp->rq_vers].nproc == 0) {
368                 svcerr_noproc(transp);
369                 goto done;
370         }
371         if (rqstp->rq_proc >= ph->prog[rqstp->rq_vers].nproc) {
372                 svcerr_noproc(transp);
373                 goto done;
374         }
375
376         if (rqstp->rq_proc == NULLPROC) {
377                 if (debug) fprintf(stderr, "rpc.cmsd: ping\n");
378                 (void)svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL);
379                 goto done;
380         }
381
382         /* assert - the program number, version and proc numbers are valid */
383         proc = &(ph->prog[rqstp->rq_vers].vers[rqstp->rq_proc]);
384         argument = (char*)calloc(proc->len_arg, sizeof(char));
385         if (!svc_getargs(transp, proc->xdr_arg, argument)) {
386                 svcerr_decode(transp);
387                 goto done;
388         }
389
390         result = (*proc->proc)(argument, rqstp);
391         if (result != NULL && !svc_sendreply(transp, proc->xdr_res, result)) {
392                 svcerr_systemerr(transp);
393         }
394
395         if (!svc_freeargs(transp, proc->xdr_arg, argument)) {
396                 (void)fprintf(stderr, "unable to free arguments");
397                 exit(1);
398         }
399         free(argument);
400
401 done:
402         rpc_in_process = 0;
403
404         /* exit if we have received the SIGHUP signal */
405         if (received_sighup == 1) {
406                 if (debug)
407                         fprintf(stderr, "rpc.cmsd: received SIGHUP, %s",
408                                 "exiting after finished processing\n");
409                 exit(0);
410         }
411 }
412
413 /*
414  * Signal handler for SIGHUP.
415  * If we are in the middle of processing a client request,
416  * finish processing before we exit.
417  */
418 static void
419 sighup_handler(int sig_num)
420 {
421         if (debug)
422                 fprintf(stderr, "rpc.cmsd: sighup received\n");
423
424         if (rpc_in_process == 0) {
425                 if (debug)
426                         fprintf(stderr, "rpc.cmsd: exit from sighup_handler\n");
427                 exit(0);
428         } else {
429                 if (debug)
430                         fprintf(stderr, "rpc.cmsd: set received_sighup to 1\n");
431                 received_sighup = 1;
432         }
433 }
434
435 /*
436  * garbage_collection_time (in min) is the time to do garbage collection
437  * each day
438  * This routine returns the difference between the first garbage collection
439  * time and now so that the calling routine can set the alarm.
440  */
441 static int
442 _GetFirstGarbageCollectionTime()
443 {
444         int n=0, midnight=0, gtime=0;
445
446         n = time(0);
447
448         /* try today first */
449         midnight = next_ndays(n, 0);
450         gtime = next_nmins(midnight, garbage_collection_time);
451
452         if (gtime < n) {
453                 /* the first garbage collection will be done tomorrow */
454                 midnight = next_ndays(n, 1);
455                 gtime = next_nmins(midnight, garbage_collection_time);
456         }
457
458         return (gtime - n);
459 }
460
461 static void
462 init_alarm()
463 {
464         int next;
465         extern void garbage_collect();
466         extern void debug_switch();
467
468 #if defined(SVR4) && !defined(linux)
469         extern void (*sigset(int, void (*)(int)))(int);
470         sigset(SIGUSR1, garbage_collect);
471         sigset(SIGALRM, garbage_collect);
472         sigset(SIGUSR2, debug_switch);
473 #else
474         signal(SIGUSR1, garbage_collect);
475         signal(SIGALRM, garbage_collect);
476         signal(SIGUSR2, debug_switch);
477 #endif /* SVR4 */
478
479         next = _GetFirstGarbageCollectionTime();
480         alarm((unsigned) next);
481 }
482
483 main(int argc, char **argv)
484 {
485         u_long version;
486         program_handle ph = newph();
487         struct passwd *pw;
488         struct group *gr;
489         struct rlimit rl;
490         struct sockaddr_in saddr;
491         int asize = sizeof (saddr);
492         SVCXPRT *tcp_transp = (SVCXPRT *)-1;
493         SVCXPRT *udp_transp = (SVCXPRT *)-1;
494         int     fd, error;
495
496 #if defined(SunOS) || defined(USL) || defined(__uxp__)
497         struct netconfig *nconf_udp;
498         struct netconfig *nconf_tcp;
499         struct t_info info;
500 #if !defined(USL) || (defined(USL) && (OSMAJORVERSION > 1))
501         char mname[FMNAMESZ+1];
502 #endif
503 #endif /* SunOS || USL */
504
505         pw = (struct passwd *)getpwnam("daemon");
506         gr = (struct group *)getgrnam("daemon");
507         if (pw != NULL) 
508                 daemon_uid = pw->pw_uid;
509         else
510                 daemon_uid = 1;
511         if (gr != NULL)
512                 daemon_gid = gr->gr_gid;
513         else 
514                 daemon_gid = 1;
515
516         parse_args(argc, argv);
517
518         /* check to see if we are started by inetd */
519         if (getsockname(0, (struct sockaddr *)&saddr, &asize) == 0) {
520
521                 standalone = 0;
522
523 #if defined(SunOS) || defined(USL) || defined(__uxp__)
524 #if !defined(USL) || (defined(USL) && (OSMAJORVERSION > 1))
525                 /* we need a TLI endpoint rather than a socket */
526                 if (ioctl(0, I_LOOK, mname) != 0) {
527                         perror("rpc.cmsd: ioctl failed to get module name");
528                         exit(1);
529                 }
530                 if (strcmp(mname, "sockmod") == 0) {
531                         /* Change socket fd to TLI fd */
532                         if (ioctl(0, I_POP, 0) || ioctl(0, I_PUSH, "timod")) {
533                                 perror("rpc.cmsd: ioctl I_POP/I_PUSH failed");
534                                 exit(1);
535                         }
536                 } else if (strcmp(mname, "timod") != 0) {
537                         fprintf(stderr, "rpc.cmsd: fd 0 is not timod\n");
538                         exit(1);
539                 }
540 #else  /* !USL || (USL && OSMAJORVERSION > 1) */
541                 if (ioctl(0, I_POP, 0) || ioctl(0, I_PUSH, "timod")) {
542                         perror("rpc.cmsd: ioctl I_POP/I_PUSH failed");
543                         exit(1);
544                 }
545 #endif /* !USL || (USL && OSMAJORVERSION > 1) */
546
547         } else if (t_getinfo(0, &info) == 0) {
548                 standalone = 0;
549
550 #endif /* SunOS || USL */
551
552         } else
553                 standalone = 1;
554
555         /*
556          * if it is started by inetd, make stderr to be
557          * output to console.
558          */
559         if (!standalone) {
560                 if ((fd = open ("/dev/console", O_WRONLY)) >= 0) {
561                         if (fd != 2) {
562                                 dup2(fd, 2);
563                                 close (fd);
564                         }
565                 }
566         }
567
568         /* Set up private directory and switch euid/egid to daemon. */
569         umask (S_IWOTH);
570         init_dir();
571
572         /* Don't allow multiple cms processes running in the same host. */
573         if ((error = lock_it()) != 0 && !standalone) {
574                 /* we are invoked by inetd but another rpc.cmsd
575                  * is alreay running, so send SIGHUP to it
576                  */
577
578                 send_hup();
579
580                 /* try to lock it again */
581                 if (lock_it() != 0) {
582                         if (debug)
583                                 fprintf(stderr, "cm: rpc.cmsd is still running\n");
584                         exit(0);
585                 }
586
587         } else if (error != 0) {
588                 fprintf(stderr, "rpc.cmsd: rpc.cmsd is already running.\n");
589                 exit(0);
590         }
591
592         /* use signal because we only need it once */
593         signal(SIGHUP, sighup_handler);
594
595
596 #if defined(SunOS) || defined(USL) || defined(__uxp__)
597         /* raise the soft limit of number of file descriptor */
598         /* this is to prevent the backend from running out of open file des */
599         getrlimit(RLIMIT_NOFILE, &rl);
600         rl.rlim_cur = (rl.rlim_max <= 256) ? rl.rlim_max : 256;
601         setrlimit(RLIMIT_NOFILE, &rl);
602 #endif
603
604 #if defined(SunOS) || defined(USL) || defined(__uxp__)
605         nconf_udp = getnetconfigent("udp");
606         nconf_tcp = getnetconfigent("tcp");
607
608         for (version = 0; version < ph->nvers; version++) {
609                 /* don't register unsupported versions: */
610                 if (ph->prog[version].nproc == 0) continue;
611
612                 if (standalone) {
613                         rpcb_unset(ph->program_num, version, NULL);
614                         if (debug)
615                                 fprintf(stderr,
616                                         "rpc.cmsd: rpcb_unset for version %ld\n",
617                                         version);
618                 }
619
620                 /* brought up by inetd, use fd 0 which must be a TLI fd */
621                 if (udp_transp == (SVCXPRT *)-1) {
622                         udp_transp = svc_tli_create(standalone ? RPC_ANYFD : 0,
623                                 nconf_udp, (struct t_bind*) NULL, 0, 0); 
624
625                         if (udp_transp == NULL) {
626                                 t_error("rtable_main.c: svc_tli_create(udp)");
627                                 exit(2);
628                         }
629                 }
630
631                 if (svc_reg(udp_transp, ph->program_num, version, program,
632                                 standalone ? nconf_udp : NULL) == 0) {
633                         t_error("rtable_main.c: svc_reg");
634                         exit(3);
635                 }
636
637                 /* Set up tcp for calls that potentially return */
638                 /* large amount of data.  This transport is not */
639                 /* registered with inetd so need to register it */
640                 /* with rpcbind ourselves.                      */
641
642                 rpcb_unset(ph->program_num, version, nconf_tcp);
643
644                 if (tcp_transp == (SVCXPRT *)-1) {
645                         tcp_transp = svc_tli_create(RPC_ANYFD, nconf_tcp,
646                                         (struct t_bind *)NULL, 0, 0);
647
648                         if (tcp_transp == NULL) {
649                                 t_error("rtable_main.c: svc_til_create(tcp)");
650                                 exit(2);
651                         }
652                 }
653
654                 if (svc_reg(tcp_transp, ph->program_num, version, program,
655                                 nconf_tcp) == 0) {
656                         t_error("rtable_main.c: svc_reg(tcp)");
657                         exit(3);
658                 }
659         }/*for*/
660
661         if (nconf_udp)
662                 freenetconfigent(nconf_udp);
663         if (nconf_tcp)
664                 freenetconfigent(nconf_tcp);
665
666 #else
667
668         for (version = 0; version < ph->nvers; version++) {
669                 /* don't register unsupported versions: */
670                 if (ph->prog[version].nproc == 0) continue;
671
672 #ifndef HPUX
673                 if (standalone)
674 #endif
675                         (void) pmap_unset(ph->program_num, version);
676
677                 if (udp_transp == (SVCXPRT *)-1) {
678                         udp_transp = svcudp_create(standalone ? RPC_ANYSOCK : 0
679 #if defined(_AIX) || defined(hpV4) || defined(__osf__) || defined(linux)
680                                         );
681 #else
682                                         ,0,0);
683 #endif
684                         if (udp_transp == NULL) {
685                                 (void)fprintf(stderr,
686                                 "rtable_main.c: cannot create udp service.\n");
687                                 exit(1);
688                         }
689                 }
690
691 #ifndef HPUX
692                 if (!svc_register(udp_transp, ph->program_num, version, program,
693                                 standalone ? IPPROTO_UDP : 0)) {
694 #else
695                 if (!svc_register(udp_transp, ph->program_num, version, program,
696                                 IPPROTO_UDP)) {
697 #endif
698                         (void)fprintf(stderr, "rtable_main.c: unable to register");
699                         exit(1);
700                 }
701
702                 /* Set up tcp for calls that potentially return */
703                 /* large amount of data.  This transport is not */
704                 /* registered with inetd so need to register it */
705                 /* with rpcbind ourselves.                      */
706
707                 if (tcp_transp == (SVCXPRT *)-1) {
708                         tcp_transp = svctcp_create(RPC_ANYSOCK, 0, 0);
709                         if (tcp_transp == NULL) {
710                                 (void)fprintf(stderr,
711                                 "rtable_main.c: cannot create tcp service.\n");
712                                 exit(1);
713                         }
714                 }
715
716                 if (!svc_register(tcp_transp, ph->program_num, version, program,
717                                 IPPROTO_TCP)) {
718                         (void)fprintf(stderr, "rtable_main.c: unable to register(tcp)");
719                         exit(1);
720                 }
721         }
722
723 #endif /* SunOS || USL */
724
725 #ifndef AIX
726 #ifdef HPUX
727         setgid (daemon_gid);
728         setuid (daemon_uid);
729 #else
730         setegid (daemon_gid);
731         seteuid (daemon_uid);
732 #endif /* HPUX */
733 #endif /* AIX */
734
735         init_time();
736         init_alarm();
737         _DtCm_init_hash();
738
739         svc_run();
740
741         (void)fprintf(stderr, "rpc.cmsd: svc_run returned\n");
742         return(1);
743 }
744