source: trunk/athena/bin/lert/lert.c @ 17860

Revision 17860, 11.1 KB checked in by zacheiss, 22 years ago (diff)
Implement v2 of the protocol, which uses krb5 for authentication. Leave v1 protocol support in the server for backwards compatability for now. Remove unused variables and modernize the declaration of main(), too.
Line 
1/* Copyright 1994, 1999 by the Massachusetts Institute of Technology.
2 *
3 * Permission to use, copy, modify, and distribute this
4 * software and its documentation for any purpose and without
5 * fee is hereby granted, provided that the above copyright
6 * notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting
8 * documentation, and that the name of M.I.T. not be used in
9 * advertising or publicity pertaining to distribution of the
10 * software without specific, written prior permission.
11 * M.I.T. makes no representations about the suitability of
12 * this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 */
15
16/* This is the client for the lert system. */
17
18static const char rcsid[] = "$Id: lert.c,v 1.12 2002-09-01 03:31:25 zacheiss Exp $";
19
20#include <sys/types.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <sys/time.h>
24
25#include <errno.h>
26#include <fcntl.h>
27#include <netdb.h>
28#include <netinet/in.h>
29#include <pwd.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include <hesiod.h>
36#include <krb5.h>
37
38#include "lert.h"
39
40#define argis(a, b) (!strcmp(*arg + 1, a) || !strcmp(*arg + 1, b))
41
42static struct timeval timeout = { LERT_TIMEOUT, 0 };
43static int error_messages = TRUE;
44
45static void bombout(int mess);
46
47static void usage(char *pname)
48{
49  fprintf(stderr,
50          "Usage: %s [-zephyr|-z] [-mail|-m] [-no|-n] [-quiet|-q] [-server|-s server]\n", pname);
51}
52
53static char *lert_says(int no_more, char *server)
54{
55  krb5_context context = NULL;
56  krb5_auth_context auth_con = NULL;
57  krb5_ccache ccache = NULL;
58  krb5_data auth, packet, msg_data;
59  krb5_error_code problem = 0;
60  void *hes_context = NULL;
61  char *lert_host, *message;
62  struct hostent *hp;
63  struct servent *sp;
64  struct sockaddr_in sin;
65  int plen, packetsize, s, tries, gotit, nread;
66  unsigned char *pktbuf;
67  fd_set readfds;
68
69  memset(&auth, 0, sizeof(krb5_data));
70  memset(&packet, 0, sizeof(krb5_data));
71  memset(&msg_data, 0, sizeof(krb5_data));
72
73  /* Find out where lert lives. (Note the presumption that there is
74   * only one lert!)
75   */
76  lert_host = NULL;
77  if (server)
78    lert_host = server;
79  else if (hesiod_init(&hes_context) == 0)
80    {
81      char **slocs = hesiod_resolve(hes_context, LERT_SERVER, LERT_TYPE);
82      if (slocs)
83        {
84          lert_host = strdup(slocs[0]);
85          if (!lert_host)
86            bombout(ERR_MEMORY);
87          hesiod_free_list(hes_context, slocs);
88        }
89    }
90  if (!lert_host)
91    {
92      lert_host = strdup(LERT_HOME);
93      if (!lert_host)
94        bombout(ERR_MEMORY);
95    }
96
97  hp = gethostbyname(lert_host);
98  if (hp == NULL)
99    bombout(ERR_HOSTNAME);
100
101  memset(&sin, 0, sizeof(sin));
102  sin.sin_family = hp->h_addrtype;
103  memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
104
105  /* Find out what port lert is on. */
106  sin.sin_port = htons(LERT_PORT);
107  sp = getservbyname(LERT_SERVED, LERT_PROTO);
108  if (sp)
109    sin.sin_port = sp->s_port;
110  else if (hes_context)
111    {
112      sp = hesiod_getservbyname(hes_context, LERT_SERVED, LERT_PROTO);
113      sin.sin_port = sp->s_port;
114      hesiod_free_servent(hes_context, sp);
115    }
116   
117  if (hes_context)
118    hesiod_end(hes_context);
119
120  problem = krb5_init_context(&context);
121  if (problem)
122    goto out;
123
124  problem = krb5_auth_con_init(context, &auth_con);
125  if (problem)
126    goto out;
127
128  problem = krb5_cc_default(context, &ccache);
129  if (problem)
130    goto out;
131
132  problem = krb5_mk_req(context, &auth_con, 0, LERT_SERVICE, lert_host,
133                        NULL, ccache, &auth);
134  if (problem)
135    goto out;
136
137  /* Lert's basic protocol:
138   * Client sends version, one byte query code, and authentication.
139   */
140  plen = LERT_LENGTH + sizeof(int) + auth.length;
141  pktbuf = malloc(plen);
142  if (!pktbuf)
143    bombout(ERR_MEMORY);
144  pktbuf[0] = LERT_VERSION;
145  pktbuf[1] = no_more;
146  memcpy(pktbuf + LERT_LENGTH, auth.data, auth.length);
147
148  s = socket(AF_INET, SOCK_DGRAM, 0);
149  if (s < 0)
150    bombout(ERR_SOCKET);
151  if (connect(s, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0)
152    bombout(ERR_CONNECT);
153
154  gotit = 0;
155  for (tries = 0; tries < RETRIES && !gotit; tries++)
156    {
157      if (send(s, pktbuf, plen, 0) < 0)
158        bombout(ERR_SEND);
159      FD_ZERO(&readfds);
160      FD_SET(s, &readfds);
161      gotit = select(s + 1, &readfds, NULL, NULL, &timeout) == 1;
162    }
163  free(pktbuf);
164  if (tries == RETRIES)
165    bombout(ERR_TIMEOUT);
166
167  /* Read the response. */
168  packetsize = 2048;
169  pktbuf = malloc(packetsize);
170  if (!pktbuf)
171    bombout(ERR_MEMORY);
172  plen = 0;
173  while (1)
174    {
175      nread = recv(s, pktbuf + plen, packetsize - plen, 0);
176      if (nread < 0)
177        bombout(ERR_RCV);
178      plen += nread;
179
180      if (nread < packetsize - plen)
181        break;
182
183      packetsize *= 2;
184      pktbuf = realloc(pktbuf, packetsize);
185      if (!pktbuf)
186        bombout(ERR_MEMORY);
187    }
188
189  packet.data = pktbuf;
190  packet.length = nread;
191
192  problem = krb5_auth_con_genaddrs(context, auth_con, s,
193                                   KRB5_AUTH_CONTEXT_GENERATE_REMOTE_ADDR |
194                                   KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
195  if (problem)
196    goto out;
197
198  /* Now close the socket. */
199  shutdown(s, 2);
200  close(s);
201
202  /* Clear auth_context flags so we don't need to manually set up a
203   * replay cache.
204   */
205  problem = krb5_auth_con_setflags(context, auth_con, 0);
206  if (problem)
207    goto out;
208
209  problem = krb5_rd_priv(context, auth_con, &packet, &msg_data, NULL);
210  if (problem)
211    goto out;
212
213  if (msg_data.length == 0)
214    return NULL;
215
216  /* At this point, we have a packet.  Check it out:
217   * [0] LERT_VERSION
218   * [1] code response
219   * [2 on] data...
220   */
221
222  if (msg_data.data[0] != LERT_VERSION)
223    bombout(ERR_VERSION);
224
225  if (msg_data.data[1] == LERT_MSG)
226    {
227      /* We have a message from the server. */
228      message = malloc(msg_data.length - LERT_CHECK + 1);
229      if (!message)
230        bombout(ERR_MEMORY);
231      memcpy(message, msg_data.data + LERT_CHECK,
232           msg_data.length - LERT_CHECK);
233      message[msg_data.length - LERT_CHECK] = '\0';
234    }
235  else
236    message = NULL;
237  free(pktbuf);
238
239  return message;
240
241 out:
242  /* krb5 library checks if this is allocated so we don't have to. */
243  krb5_free_data_contents(context, &auth);
244  krb5_free_data_contents(context, &msg_data);
245
246  if (ccache)
247    krb5_cc_close(context, ccache);
248
249  if (auth_con)
250    krb5_auth_con_free(context, auth_con);
251
252  bombout(problem);
253}
254
255/* General error reporting */
256static void bombout(int mess)
257{
258  if (error_messages)
259    {
260      fprintf(stderr, "lert: ");
261      switch(mess)
262        {
263        case ERR_KERB_CRED:
264          fprintf(stderr, "Error getting kerberos credentials.\n");
265          break;
266        case ERR_KERB_AUTH:
267          fprintf(stderr, "Error getting kerberos authentication.\n");
268          fprintf(stderr, "Are your tickets valid?\n");
269          break;
270        case ERR_TIMEOUT:
271          fprintf(stderr, "Timed out waiting for response from server.\n");
272          break;
273        case ERR_SERVER:
274          fprintf(stderr, "Unable to contact server.\n");
275          break;
276        case ERR_SERVED:
277          fprintf(stderr, "Bad string from server.\n");
278          break;
279        case ERR_SEND:
280          fprintf(stderr, "Error in send: %s\n", strerror(errno));
281          break;
282        case ERR_RCV:
283          fprintf(stderr, "Error in recv: %s\n", strerror(errno));
284          break;
285        case ERR_USER:
286          fprintf(stderr, "Could not get your name to send messages\n");
287          break;
288        case NO_PROCS:
289          fprintf(stderr, "Error running child processes: %s\n",
290                  strerror(errno));
291          break;
292        case ERR_MEMORY:
293          fprintf(stderr, "Out of memory\n");
294          break;
295        case ERR_FILE:
296          fprintf(stderr, "Could not read message file\n");
297          break;
298        default:
299          fprintf(stderr, "%s while accessing the database\n",
300                  error_message(mess));
301          break;
302        }
303      fprintf(stderr, "Please try again later.\n");
304      fprintf(stderr,
305              "If this problem persists, please report it to a consultant.\n");
306    }
307  exit(1);
308}
309
310/* Send a message from lert via zephyr. */
311static void zephyr_message(char *user, char *header, char *message)
312{
313  FILE *p;
314  char *cmd;
315
316  cmd = malloc(11 + strlen(user));
317  if (!cmd)
318    bombout(ERR_MEMORY);
319  sprintf(cmd, "zwrite -q %s", user);
320
321  p = popen(cmd, "w");
322  if (!p)
323    bombout(NO_PROCS);
324  fputs("@bold{", p);
325  fputs(header, p);
326  fputs("}", p);
327  fputs(message, p);
328  pclose(p);
329}
330
331/* Send a message from lert via mail. */
332static void mail_message(char *user, char *subject,
333                         char *header, char *message)
334{
335  FILE *p;
336  char *cmd;
337
338  cmd = malloc(20 + strlen(subject) + strlen(user));
339  if (!cmd)
340    bombout(ERR_MEMORY);
341  sprintf(cmd, "mhmail %s -subject \"%s\"", user, subject);
342
343  p = popen(cmd, "w");
344  if (!p)
345    bombout(NO_PROCS);
346  fputs(header, p);
347  fputs(message, p);
348  pclose(p);
349}
350
351/* Print a message from lert to stdout. */
352static void cat_message(char *header, char *message)
353{
354  fputs(header, stdout);
355  fputs(message, stdout);
356}         
357
358/* Read a file from the lert directory. */
359static char *read_file(char *name)
360{
361  struct stat st;
362  int fd;
363  char *buf;
364
365  fd = open(name, O_RDONLY);
366  if (fd < 0 || fstat(fd, &st) < 0)
367    bombout(ERR_FILE);
368
369  buf = malloc(st.st_size + 1);
370  if (!buf)
371    bombout(ERR_MEMORY);
372
373  if (read(fd, buf, st.st_size) < st.st_size)
374    bombout(ERR_FILE);
375  buf[st.st_size] = '\0';
376  close(fd);
377
378  return buf;
379}
380
381/* View the message from lert. */
382static void view_message(char *msgs, int type, int no_more)
383{
384  char name[sizeof(LERTS_MSG_FILES) + 2];
385  char *user, *subject = NULL;
386  char *header, *body = NULL;
387
388  if (type == LERT_Z || type == LERT_MAIL)
389    {
390      struct passwd *pw;
391
392      pw = getpwuid(getuid());
393      if (pw)
394        user = pw->pw_name;
395      else
396        {
397          user = getenv("USER");
398          if (!user)
399            user = getlogin();
400        }
401      if (!user)
402        type = LERT_CAT;
403    }
404
405  if (type == LERT_MAIL)
406    {
407      subject = read_file(LERTS_MSG_SUBJECT);
408      if (!subject)
409        subject = strdup(LERTS_DEF_SUBJECT);
410      if (!subject)
411        bombout(ERR_MEMORY);
412    }
413
414  header = read_file(no_more ? LERTS_MSG_FILES "1" : LERTS_MSG_FILES "0");
415
416  while (*msgs)
417    {
418      sprintf(name, "%s%c", LERTS_MSG_FILES, *msgs);
419      body = read_file(name);
420      switch (type)
421        {
422        case LERT_Z:
423          zephyr_message(user, header, body);
424          break;
425
426        case LERT_MAIL:
427          mail_message(user, subject, header, body);
428          break;
429
430        default:
431          cat_message(header, body);
432          break;
433        }
434
435      free(body);
436      msgs++;
437    }
438
439  free(header);
440  free(subject);
441}
442
443int main(int argc, char **argv)
444{
445  char *whoami, *message, *server = NULL;
446  int method = LERT_CAT, no_more = FALSE;
447  char **arg = argv;
448
449  whoami = strrchr(argv[0], '/');
450  if (whoami)
451    whoami++;
452  else
453    whoami = argv[0];
454
455  /* Argument Processing:
456   *    -z or -zephyr: send the message as a zephyrgram.
457   *    -m or -mail: send the message as email
458   *    -n or -no: no more messages!
459   */
460
461  while (++arg - argv < argc)
462    {
463      if (**arg == '-')
464        {
465          if (argis("zephyr", "z"))
466            method = LERT_Z;
467          else if (argis("mail","m"))
468            method = LERT_MAIL;
469          else if (argis("no", "n"))
470            no_more = TRUE;
471          else if (argis("quiet", "q"))
472            error_messages = FALSE;
473          else if (argis("server", "s"))
474            {
475              if (arg - argv < argc - 1)
476                {
477                  ++arg;
478                  server = *arg;
479                }
480              else
481                usage(whoami);
482            }
483          else
484            {
485              usage(whoami);
486              exit(1);
487            }
488        }
489    }
490  /* Get the server's string for this user. */
491  message = lert_says(no_more, server);
492  if (message)
493    {
494      view_message(message, method, no_more);
495      free(message);
496    }
497
498  return 0;
499}
Note: See TracBrowser for help on using the repository browser.