3 * Server for user registration with Moira and Kerberos.
5 * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology
6 * For copying and distribution information, please see the file
10 #include <mit-copyright.h>
12 #include <mr_private.h>
13 #include <moira_schema.h>
14 #include <moira_site.h>
17 #include <sys/types.h>
18 #include <sys/socket.h>
21 #include <sys/utsname.h>
23 #include <netinet/in.h>
40 EXEC SQL INCLUDE sqlca;
44 char *whoami, *hostname, *shorthostname;
46 char *find_usernames(char *first, char *middle, char *last);
47 int check_username_available(char *username);
48 void fixname(char *name);
49 int register_user(int uid, char *username);
50 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar);
53 reg_client *cl = NULL;
54 enum { RS_RUNNING, RS_SLEEPING, RS_EXITING } state = RS_RUNNING;
56 int main(int argc, char **argv)
58 int listener, nfds, i, clientid = 0;
59 fd_set readfds, xreadfds;
61 int nclients, clientssize;
69 whoami = strrchr(argv[0], '/');
70 whoami = whoami ? whoami + 1 : argv[0];
72 set_com_err_hook(mr_com_err);
77 com_err(whoami, errno, "reading RSA key");
82 com_err(whoami, errno, "reading HMAC key");
86 /* Read error messages */
89 com_err(whoami, errno, "reading error messages");
93 /* Connect to database */
94 EXEC SQL CONNECT :db IDENTIFIED BY :db;
98 int bufsize = 256, msglength = 0;
100 sqlglm(err_msg, &bufsize, &msglength);
101 err_msg[msglength] = 0;
102 com_err(whoami, 0, "SQL error connecting to DBMS:\n%s", err_msg);
106 /* Get my hostname */
108 h = gethostbyname(uts.nodename);
111 com_err(whoami, 0, "Couldn't resolve hostname %s", uts.nodename);
114 hostname = lowercase(xstrdup(h->h_name));
115 shorthostname = xstrdup(hostname);
116 if (strchr(shorthostname, '.'))
117 *strchr(shorthostname, '.') = '\0';
119 /* Initialize kerberos */
120 status = init_kerberos();
123 com_err(whoami, status, "initializing kerberos library");
127 /* Set up listening socket. */
128 listener = mr_listen("moira_ureg");
131 com_err(whoami, errno, "couldn't create listening socket");
135 FD_SET(listener, &xreadfds);
138 /* Initialize client array. */
141 clients = malloc(clientssize * sizeof(reg_client));
144 com_err(whoami, errno, "creating client array");
148 /* Set up signal handlers */
150 sigemptyset(&sa.sa_mask);
151 sa.sa_handler = sigshut;
152 sigaction(SIGTERM, &sa, NULL);
153 sigaction(SIGINT, &sa, NULL);
154 sigaction(SIGHUP, &sa, NULL);
155 sa.sa_handler = SIG_IGN;
156 sigaction(SIGPIPE, &sa, NULL);
158 com_err(whoami, 0, "started (pid %d)", getpid());
159 com_err(whoami, 0, rcsid);
162 while (state != RS_EXITING)
164 if (state == RS_RUNNING && stat(MOIRA_MOTD_FILE, &st) == 0)
167 com_err(whoami, 0, "found motd. reg_svr is sleeping");
169 else if (state == RS_SLEEPING && stat(MOIRA_MOTD_FILE, &st) == -1)
172 com_err(whoami, 0, "motd gone. reg_svr is running");
175 memcpy(&readfds, &xreadfds, sizeof(readfds));
176 if (select(nfds, &readfds, NULL, NULL, NULL) == -1)
179 com_err(whoami, errno, "in select");
183 if (FD_ISSET(listener, &readfds))
185 int newconn, addrlen = sizeof(struct sockaddr_in);
186 struct sockaddr_in addr;
188 newconn = accept(listener, (struct sockaddr *)&addr, &addrlen);
190 com_err(whoami, errno, "accepting new connection");
194 if (nclients > clientssize)
196 clientssize = 2 * clientssize;
197 clients = xrealloc(clients, clientssize *
201 cl = &clients[nclients - 1];
202 memset(cl, 0, sizeof(reg_client));
204 cl->lastmod = time(NULL);
205 cl->clientid = ++clientid;
206 cl->random = init_rand(cl);
207 FD_SET(newconn, &xreadfds);
212 "New connection from %s port %d (now %d client%s)",
213 inet_ntoa(addr.sin_addr), (int)ntohs(addr.sin_port),
214 nclients, nclients != 1 ? "s" : "");
218 for (i = 0; i < nclients; i++)
221 if (FD_ISSET(cl->fd, &readfds))
223 cl->lastmod = time(NULL);
226 /* We're just starting */
230 com_err(whoami, errno, "allocating read buffer");
231 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
240 /* We haven't read the length byte yet... */
241 cl->nread += read(cl->fd, cl->buf + cl->nread,
245 cl->nmax = cl->buf[1] * 256 + cl->buf[2] + 3;
246 cl->buf = realloc(cl->buf, cl->nmax + 3);
249 com_err(whoami, errno, "reallocating read buffer");
250 reply(cl, INTERNAL_ERROR, "INIT", "c", NULL,
255 else if (cl->nread == 0)
257 /* client has closed connection. setting
258 lastmod will cause it to be reaped */
264 /* We know how long the packet is supposed to be */
265 cl->nread += read(cl->fd, cl->buf + cl->nread,
266 cl->nmax - cl->nread);
267 if (cl->nread == cl->nmax)
269 parse_packet(cl, cl->buf[0], cl->nread - 3, cl->buf + 3,
270 state == RS_SLEEPING);
278 if (cl->lastmod < time(NULL) - TIMEOUT)
280 com_err(whoami, 0, "Closed connection. (now %d client%s)",
281 nclients - 1, nclients != 2 ? "s" : "");
284 FD_CLR(cl->fd, &xreadfds);
288 free(cl->suggestions);
290 clients[i] = clients[--nclients];
296 com_err(whoami, 0, "Exiting.");
299 void RIFO(reg_client *rc, int argc, char **argv)
301 EXEC SQL BEGIN DECLARE SECTION;
302 char *ufirst, *umiddle, *ulast, *id;
303 char login[USERS_LOGIN_SIZE], first[USERS_FIRST_SIZE];
304 char middle[USERS_MIDDLE_SIZE], last[USERS_LAST_SIZE];
305 char fullname[USERS_FIRST_SIZE + USERS_MIDDLE_SIZE + USERS_LAST_SIZE];
306 char class[USERS_TYPE_SIZE], pin[USERS_PIN_SIZE];
307 int uid, status, secure, sqlstatus, count;
308 EXEC SQL END DECLARE SECTION;
310 if (rc->uid || argc != 4)
312 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
321 EXEC SQL SELECT count(login) INTO :count FROM users WHERE clearid = :id;
323 /* "ORDER BY status" so that if there's both a matching state 0 entry
324 and a matching state 3 entry, we'll get the former. */
325 EXEC SQL DECLARE csr_id CURSOR FOR
326 SELECT login, unix_uid, status, secure, pin, first, middle, last, type
327 FROM users WHERE clearid = :id ORDER BY status;
328 EXEC SQL OPEN csr_id;
331 EXEC SQL FETCH csr_id INTO :login, :uid, :status,
332 :secure, :pin, :first, :middle, :last, :class;
342 /* It's possible they have both a deleted account and a status 8
343 * account. We can't compensate for that in the ORDER BY clause
344 * above, so check here. If they have more than one entry and the
345 * first one we get is deleted, skip it.
347 if (status == US_DELETED && count > 1)
350 /* Check names, allowing for the possibility that Moira and the
351 user might have them split up differently. eg, Mary/Ann/Singleton
352 vs. Mary Ann/Singleton. */
353 if (strcasecmp(last, ulast) && strncasecmp(last, ulast, strlen(last)) &&
354 strncasecmp(last, ulast, strlen(ulast)))
356 if (strlen(last) > 3 && strlen(ulast) < 3)
358 if (strcasecmp(first, ufirst) &&
359 strncasecmp(first, ufirst, strlen(first)) &&
360 strncasecmp(first, ufirst, strlen(ufirst)))
362 if (strlen(first) > 3 && strlen(ufirst) < 3)
364 if (!*ufirst && !*ulast)
367 /* Ignore the middle name since Moira doesn't have those reliably */
370 sqlstatus = sqlca.sqlcode;
371 EXEC SQL CLOSE csr_id;
375 reply(rc, NOT_FOUND_IN_DATABASE, "GETN", "d", NULL);
383 case US_ENROLL_NOT_ALLOWED:
384 case US_REGISTERED_KERBEROS_ONLY:
385 reply(rc, ALREADY_REGISTERED, "INIT", "c", NULL, login);
389 reply(rc, ACCOUNT_DELETED, "INIT", "c", NULL, login);
393 reply(rc, NOT_ELIGIBLE, "INIT", "c", NULL);
400 rc->user_status = status;
402 sprintf(fullname, "%s %s%s%s", first, middle, *middle ? " " : "", last);
403 if (!strcmp(class, "MITS"))
404 strcpy(class, "STAFF");
410 com_err(whoami, errno, "in RIFO");
411 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
417 rc->reserved_username = 1;
418 rc->username = strdup(login);
421 com_err(whoami, errno, "in RIFO");
422 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
428 rc->suggestions = find_usernames(first, middle, last);
429 if (!rc->suggestions && errno)
431 com_err(whoami, errno, "in RIFO");
432 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(errno));
440 reply(rc, FOUND, "GETI", "c", NULL, fullname, class);
442 reply(rc, FOUND, "GETW", "c", NULL, fullname, class);
444 else if (!rc->username)
445 reply(rc, FOUND, "GETL", "c", rc->suggestions, fullname, class);
448 if (rc->user_status == US_NO_LOGIN_YET ||
449 rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
451 status = check_kerberos(login);
452 if (status == MR_SUCCESS &&
453 rc->user_status != US_NO_LOGIN_YET_KERBEROS_ONLY)
454 status = register_user(rc->uid, login);
455 if (status == MR_IN_USE)
457 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
461 else if (status == MR_DOWN)
463 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
466 else if (status != MR_SUCCESS)
468 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL,
469 error_message(status));
473 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
477 void SWRD(reg_client *rc, int argc, char **argv)
482 if (!rc->id || argc != 6)
484 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
488 getwordlist(rc->id, words);
489 for (i = 0; i < 6; i++)
491 if (strcasecmp(strtrim(argv[i]), words[i]))
496 reply(rc, BAD_SIX_WORDS, "GETW", "d", NULL);
503 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
505 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
508 void SPIN(reg_client *rc, int argc, char **argv)
510 EXEC SQL BEGIN DECLARE SECTION;
511 char pin[USERS_PIN_SIZE];
512 EXEC SQL END DECLARE SECTION;
514 if (!rc->id || argc != 1)
516 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
520 EXEC SQL SELECT pin INTO :pin FROM users WHERE clearid = :rc->id
521 AND status = :rc->user_status;
523 if (strcmp(argv[0], pin) != 0)
525 reply(rc, BAD_PIN, "GETI", "d", NULL);
532 reply(rc, NO_MESSAGE, "GETL", "c", rc->suggestions);
535 register_user(rc->uid, rc->username);
536 reply(rc, FORCED_USERNAME, "GETP", "c", NULL, rc->username);
540 void CLGN(reg_client *rc, int argc, char **argv)
546 if (!rc->uid || rc->id || rc->username || argc != 1)
548 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
554 /* make sure someone's not trying to overrun reply */
555 if (strlen(login) > 100)
557 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
562 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
563 (login[0] == '_') || isdigit(login[0]))
565 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
566 3, USERS_LOGIN_SIZE - 1);
570 for (i = 0; i < strlen(login); i++)
572 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
574 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
575 3, USERS_LOGIN_SIZE - 1);
580 status = check_kerberos(login);
581 if (status == MR_SUCCESS)
583 status = check_username_available(login);
584 if (status == MR_SUCCESS)
586 reply(rc, USERNAME_AVAILABLE, "LOGC", "c", login, login);
591 if (status == MR_IN_USE)
593 if (rc->reserved_username)
595 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
599 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
602 else if (status == MR_DOWN)
604 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
607 else if (status != MR_SUCCESS)
609 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
614 void LOGN(reg_client *rc, int argc, char **argv)
620 if (!rc->uid || rc->id || rc->username || argc != 1)
622 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
628 /* make sure someone's not trying to overrun reply */
629 if (strlen(login) > 100)
631 com_err(whoami, 0, "Buffer overrun attempted? Closing connection");
636 if ((strlen(login) < 3) || (strlen(login) > USERS_LOGIN_SIZE - 1) ||
637 (login[0] == '_') || isdigit(login[0]))
639 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
640 3, USERS_LOGIN_SIZE - 1);
644 for (i = 0; i < strlen(login); i++)
646 if (!islower(login[i]) && !isdigit(login[i]) && (login[i] != '_'))
648 reply(rc, BAD_USERNAME, "GETL", "c", rc->suggestions, login,
649 3, USERS_LOGIN_SIZE - 1);
654 status = check_kerberos(login);
655 if (status == MR_SUCCESS)
657 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
658 EXEC SQL UPDATE users SET login = :login WHERE unix_uid = :rc->uid;
660 status = register_user(rc->uid, login);
662 if (status == MR_IN_USE)
664 if (rc->reserved_username)
666 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
670 reply(rc, USERNAME_UNAVAILABLE, "GETL", "c", rc->suggestions);
673 else if (status == MR_DOWN)
675 reply(rc, DATABASE_CLOSED, "INIT", "c", NULL);
678 else if (status != MR_SUCCESS)
680 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, error_message(status));
684 rc->username = strdup(login);
687 com_err(whoami, errno, "in LOGN");
688 reply(rc, INTERNAL_ERROR, "INIT", "c", NULL, "Out of memory");
691 reply(rc, USERNAME_OK, "GETP", "c", NULL, login);
695 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^@ - ^O */
696 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ^P - ^_ */
697 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* SPACE - / */
698 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, /* 0 - ? */
699 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* : - O */
700 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, /* P - _ */
701 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* ` - o */
702 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, /* p - ^? */
703 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
704 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
705 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
706 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
707 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
708 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
709 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
710 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
713 void PSWD(reg_client *rc, int argc, char **argv)
716 char *password = argv[0], *p;
717 EXEC SQL BEGIN DECLARE SECTION;
718 char *login = rc->username;
719 char potype[USERS_POTYPE_SIZE];
720 EXEC SQL END DECLARE SECTION;
722 if (!rc->username || rc->id || argc != 1)
724 reply(rc, PROTOCOL_ERROR, "INIT", "c", NULL);
728 /* password quality checking */
729 if (strlen(password) < 4)
731 reply(rc, PASSWORD_SHORT, "GETP", "c", NULL);
735 if (strlen(password) < 7)
737 for (p = password + 1; *p; p++)
739 if (ctypes[*p] != ctypes[*(p - 1)])
744 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
749 if (!strcasecmp(password, "GykoR-66") ||
750 !strcasecmp(password, "slaRooBey") ||
751 !strcasecmp(password, "krang-its") ||
752 !strcasecmp(password, "2HotPeetzas") ||
753 !strcasecmp(password, "ItzAGurl"))
755 reply(rc, PASSWORD_SAMPLE, "GETP", "c", NULL);
759 status = register_kerberos(rc->username, password);
760 if (status == MR_QUALITY)
762 reply(rc, PASSWORD_SIMPLE, "GETP", "c", NULL);
765 else if (status == MR_IN_USE)
767 reply(rc, RESERVED_USERNAME_UNAVAILABLE, "INIT", "c", NULL,
773 com_err(whoami, status, "registering username with Kerberos");
774 reply(rc, KADM_ERROR, "INIT", "c", NULL, error_message(status));
778 if (rc->user_status == US_NO_LOGIN_YET_KERBEROS_ONLY)
779 EXEC SQL UPDATE users SET status = 9 WHERE login = :login;
781 EXEC SQL UPDATE users SET status = 1 WHERE login = :login;
784 EXEC SQL SELECT potype INTO :potype FROM users WHERE login = :login;
785 if (!strcmp(potype, "EXCHANGE"))
786 reply(rc, DONE, "INIT", "c", NULL, rc->username, "http://owa.mit.edu");
788 reply(rc, DONE, "INIT", "c", NULL, rc->username, "http://webmail.mit.edu");
791 void QUIT(reg_client *rc, int argc, char **argv)
795 /* Register a user in Moira */
796 int register_user(int uid, char *username)
798 EXEC SQL BEGIN DECLARE SECTION;
799 char class[USERS_TYPE_SIZE];
800 EXEC SQL END DECLARE SECTION;
801 char uidbuf[10], *qargv[3], *motd = NULL;
804 status = mr_connect(hostname);
808 status = mr_motd(&motd);
815 status = mr_krb5_auth("reg_svr");
818 com_err(whoami, status, "authenticating to moira");
823 EXEC SQL SELECT type INTO :class FROM users WHERE unix_uid = :uid;
825 sprintf(uidbuf, "%d", uid);
829 /* Incoming students should be given Exchange poboxes.
830 * Doesn't work for undergrads in the class of 2100 or higher.
832 if (!strcmp(strtrim(class), "G") || !strncmp(class, "FALL", 4) ||
833 !strncmp(class, "SPRING", 5) || !strncmp(class, "SUMMER", 6) ||
834 !strncmp(class, "20", 2))
836 com_err(whoami, 0, "assigning EXCHANGE pobox to user %s, class %s", username, class);
837 qargv[2] = "EXCHANGE";
841 com_err(whoami, 0, "assigning IMAP pobox to user %s, class %s", username, class);
845 status = mr_query("register_user", 3, qargv, NULL, NULL);
851 /* Find some typical available usernames */
853 char *uname_patterns[] = {
855 "fmllllll", /* jmdoe... (last name truncated) */
856 "flllllll", /* jdoe.... ("") */
857 "llllllll", /* doe..... ("") */
864 int num_patterns = sizeof(uname_patterns) / sizeof(char *);
866 char *find_usernames(char *first, char *middle, char *last)
868 EXEC SQL BEGIN DECLARE SECTION;
869 char username[2 * USERS_LOGIN_SIZE];
871 EXEC SQL END DECLARE SECTION;
873 char *pp, *up, *fp, *mp, *lp, *unames = NULL;
879 for (pat = 0; pat < num_patterns; pat++)
885 for (pp = uname_patterns[pat]; *pp; pp++)
895 if (up - username + strlen(first) < USERS_LOGIN_SIZE)
896 up += sprintf(up, "%s", first);
914 if (up - username + strlen(last) < USERS_LOGIN_SIZE)
915 up += sprintf(up, "%s", last);
923 if (strlen(username) < 3 || strlen(username) >= USERS_LOGIN_SIZE)
926 EXEC SQL SELECT COUNT(login) INTO :count FROM users
927 WHERE login = :username;
935 EXEC SQL SELECT COUNT(name) INTO :count FROM list
936 WHERE name = :username;
945 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
946 WHERE label = :username;
958 unames = realloc(unames, strlen(unames) + strlen(username) + 3);
961 strcat(unames, ", ");
962 strcat(unames, username);
966 unames = strdup(username);
976 /* unames will be NULL if we couldn't suggest a username. Clear
977 errno so the caller can distinguish this from an error case. */
982 /* This does the database-side checks to make sure a username is
985 int check_username_available(char *username)
989 EXEC SQL SELECT COUNT(login) INTO :count FROM users
990 WHERE login = :username;
996 EXEC SQL SELECT COUNT(name) INTO :count FROM list
997 WHERE name = :username;
1003 EXEC SQL SELECT COUNT(label) INTO :count FROM filesys
1004 WHERE label = :username;
1010 EXEC SQL SELECT COUNT(login) INTO :count FROM userhistory
1011 WHERE login = :username;
1020 void fixname(char *name)
1024 for (s = d = name; *s; s++)
1032 void *xmalloc(size_t bytes)
1034 void *buf = malloc(bytes);
1039 com_err(whoami, errno, "in xmalloc");
1043 void *xrealloc(void *ptr, size_t bytes)
1045 void *buf = realloc(ptr, bytes);
1050 com_err(whoami, errno, "in xrealloc");
1054 char *xstrdup(char *str)
1056 char *buf = strdup(str);
1061 com_err(whoami, errno, "in xstrdup");
1065 void mr_com_err(const char *whoami, long code, const char *fmt, va_list pvar)
1069 fputs(whoami, stderr);
1071 fprintf(stderr, "[#%d]", cl->clientid);
1072 fputs(": ", stderr);
1075 fputs(error_message(code), stderr);
1079 vfprintf(stderr, fmt, pvar);
1083 void sigshut(int sig)