1 /* Common functions for each GNATS client.
2    Copyright (C) 1994, 1995, 1999, 2000, 2002 Free Software Foundation, Inc.
3    Copyright (C) 2001 Dan Martinez
4    Contributed by Brendan Kehoe (brendan@cygnus.com).
5 
6 This file is part of GNU GNATS.
7 
8 GNU GNATS is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
12 
13 GNU GNATS is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more detailmails.
17 
18 You should have received a copy of the GNU General Public License
19 along with GNU GNATS; see the file COPYING.  If not, write to the Free
20 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */
21 
22 #include "gnats.h"
23 #include "gnatsd.h"
24 #include "query.h"
25 #include <sys/wait.h>
26 
27 #ifdef HAVE_MACHINE_ENDIAN_H
28 #include <machine/endian.h>			/* for htons */
29 #endif
30 
31 #ifdef HAVE_KERBEROS
32 #include <des.h>
33 #include <krb.h>
34 #ifndef HAVE_KRB_GET_ERR_TEXT
35 #define krb_get_err_text(status) krb_err_txt[status]
36 #endif
37 int remember_krb_auth_ret = 0; /* yes, I like lisp. go away. */
38 #endif
39 
40 /* For the GNATS server we'll be talking with.  */
41 static FILE *serv_write, *serv_read;
42 
43 /* If we're reading in a PR, this is it. */
44 static PR *prBeingRead = NULL;
45 
46 /* If we're reading in a list of PRs, this is where it goes. */
47 static StringList **prList;
48 
49 /* The descriptor for talking to the server in non-stdio ways.  */
50 static int sockfd, dupfd;
51 
52 /* If 1, send information as we make the query.  */
53 int debug = 0;
54 
55 static int forkedGnatsd = 0;
56 
57 static void
free_reply(Reply * r)58 free_reply (Reply *r)
59 {
60   free (r->text);
61   free ((char*)r);
62 }
63 
64 
65 static Reply *
server_reply(void)66 server_reply (void)
67 {
68   char *recvline;
69   char *p, c;
70   Reply *res = NULL;
71 
72   recvline = read_line (serv_read, NULL);
73 
74   if (recvline == NULL)
75     {
76       fprintf (stderr, "%s: error reading from server\n", program_name);
77       /* This is probably not a good idea, eh?  XXX ??? !!! */
78       safe_exit ();
79     }
80 
81   c = recvline[0];
82   if (isascii ((int) c) && isdigit((int) c))
83     {
84       for (p = &recvline[0];
85 	   *p && isascii ((int) *p) && isdigit ((int) *p);
86 	   p++)
87 	continue;
88       if (*p)
89 	{
90 	  size_t i;
91 	  res = (Reply *) xmalloc (sizeof (Reply));
92 
93 	  if (*p == '-')
94 	    {
95 	      res->type = REPLY_CONT;
96 	    }
97 	  else
98 	    {
99 	      if (*p != ' ')
100 		{
101 		  fprintf (stderr, "%s: bad type of reply from server\n",
102 			   program_name);
103 		}
104 	      res->type = REPLY_END;
105 	    }
106 	  *p = '\0';
107 	  res->state = atoi (recvline);
108 	  res->text = xstrdup (p + 1);
109 	  i = strlen (res->text) - 2;
110 	  if (res->text[i] == '\r' || res->text[i] == '\n')
111 	    {
112 	      res->text[i] = '\0';
113 	    }
114 	  else if (res->text[i + 1] == '\n')
115 	    {
116 	      res->text[i + 1] = '\0';
117 	    }
118 	}
119     }
120 
121   free (recvline);
122   return res;
123 }
124 
125 static void
readPRFromServer(PR * pr)126 readPRFromServer (PR *pr)
127 {
128   char *recvline;
129   int notdone = 1;
130 
131   while (notdone && ((recvline = read_line (serv_read, NULL)) != NULL))
132     {
133       if (debug)
134 	{
135 	  fprintf (stderr, "%s: received `%s'\n", program_name, recvline);
136 	}
137 
138       if (memcmp (recvline, ".\r\n\0", 4) == 0)
139 	{
140 	  finishReadPR (pr, 0);
141 	  notdone = 0;
142 	}
143       else
144 	{
145 	  size_t linelen = strlen (recvline);
146 	  int i;
147 
148 	  if (linelen > 0)
149 	    {
150 	      if (recvline[linelen - 2] == '\r')
151 		{
152 		  /* Only correct this if we're actually at the end of a
153 		     line.  */
154 		  recvline[linelen - 2] = '\n';
155 		  recvline[linelen - 1] = '\0';
156 		  linelen--;
157 		}
158 	    }
159 	  /* Remove escape character added by write_multiline() */
160 	  i = 0;
161 	  if (recvline[0] == '.')
162 	    {
163 	      i = 1;
164 	    }
165 	  addLineToPR (pr, recvline, recvline + i, linelen - i, 0);
166 	  recvline = NULL;
167 	}
168       if (recvline != NULL)
169 	{
170 	  free (recvline);
171 	}
172     }
173 }
174 
175 static void
readPRListFromServer(void)176 readPRListFromServer (void)
177 {
178   char *recvline;
179   int notdone = 1;
180 
181   while (notdone && ((recvline = read_line (serv_read, NULL)) != NULL))
182     {
183       if (debug)
184 	{
185 	  fprintf (stderr, "%s: received `%s'\n", program_name, recvline);
186 	}
187 
188       if (memcmp (recvline, ".\r\n\0", 4) == 0)
189 	{
190 	  notdone = 0;
191 	}
192       else
193 	{
194 	  size_t linelen = strlen (recvline);
195 	  int i;
196 
197 	  if (linelen > 0)
198 	    {
199 	      if (recvline[linelen - 2] == '\r')
200 		{
201 		  /* Only correct this if we're actually at the end of a
202 		     line.  */
203 		  recvline[linelen - 2] = '\n';
204 		  recvline[linelen - 1] = '\0';
205 		  linelen--;
206 		}
207 	    }
208 	  /* Remove escape character added by write_multiline() */
209 	  i = 0;
210 	  if (recvline[0] == '.')
211 	    {
212 	      i = 1;
213 	    }
214 	  while (recvline[i] != '\0' && isspace ((int)(unsigned char) recvline[i]))
215 	    {
216 	      i++;
217 	    }
218 	  if (isdigit ((int) recvline[i]))
219 	    {
220 	      *prList = new_string_list_ent (xstrdup (recvline + i), NULL);
221 	      prList = &((*prList)->next);
222 	    }
223 	}
224     }
225 }
226 
227 static void
read_server(FILE * outfp)228 read_server (FILE *outfp)
229 {
230   char *recvline;
231   int notdone = 1;
232 
233   while (notdone && ((recvline = read_line (serv_read, NULL)) != NULL))
234     {
235       if (debug)
236 	{
237 	  fprintf (stderr, "%s: received `%s'\n", program_name, recvline);
238 	}
239 
240       if (memcmp (recvline, ".\r\n\0", 4) == 0)
241 	{
242 	  notdone = 0;
243 	}
244       else
245 	{
246 	  int i;
247 	  if (recvline[1])
248 	    {
249 	      size_t linelen = strlen (recvline) - 2;
250 	      if (recvline[linelen] == '\r')
251 		{
252 		  /* Only correct this if we're actually at the end of a
253 		     line.  */
254 		  recvline[linelen] = '\n';
255 		  recvline[linelen + 1] = '\0';
256 		}
257 	    }
258 	  /* Remove escape character added by write_multiline() */
259 	  i = 0;
260 	  if (recvline[0] == '.')
261 	    {
262 	      i = 1;
263 	    }
264 	  fprintf (outfp, "%s", recvline + i);
265 	}
266       free (recvline);
267     }
268 }
269 
270 
271 /* Return the code of the response from the server, or 0 if there wasn't
272    any. */
273 int
get_reply(FILE * outfp)274 get_reply (FILE *outfp)
275 {
276   Reply *r;
277   int done = 0;
278   int retval = 0;
279 
280   /* Make sure anything we've written has gone to them.  */
281   if (fflush (serv_write) == EOF || ferror (serv_write))
282     {
283       fprintf (stderr, "%s: error writing to server\n", program_name);
284     }
285 
286   while (! done)
287     {
288       r = server_reply ();
289 
290       if (r != NULL)
291 	{
292 	  if (r->type != REPLY_CONT)
293 	    {
294 	      done = 1;
295 	    }
296 
297 	  if (debug)
298 	    {
299 	      fprintf (stderr, "%s: received %d `%s'\n",
300 		       program_name, r->state, r->text);
301 	    }
302 
303 	  switch (r->state)
304 	    {
305 	    case CODE_GREETING:
306 	    case CODE_OK:
307 	    case CODE_SEND_PR:
308 	    case CODE_SEND_TEXT:
309 	    case CODE_SEND_CHANGE_REASON:
310 	    case CODE_INFORMATION_FILLER:
311 	      break;
312 
313 	    case CODE_CLOSING:
314 	      if (debug)
315 		{
316 		  fprintf (stderr, "%s: server closing down\n", program_name);
317 		}
318 	      break;
319 
320 	    case CODE_PR_READY:
321 	    case CODE_TEXT_READY:
322 	      /* read it til we get a . */
323 	      if (prBeingRead != NULL)
324 		{
325 		  readPRFromServer (prBeingRead);
326 		}
327 	      else if (prList != NULL)
328 		{
329 		  readPRListFromServer ();
330 		}
331 	      else
332 		{
333 		  if (outfp != NULL)
334 		    read_server (outfp);
335 		  else
336 		    {
337 		      fprintf (stderr, "%s: unexpected %d received\n",
338 			       program_name, r->state);
339 		      safe_exit();
340 		    }
341 		}
342 	      break;
343 
344 	    case CODE_INFORMATION:
345 	      if (outfp != NULL)
346 		fprintf (outfp, "%s\n", r->text);
347 	      break;
348 
349 	    case CODE_NONEXISTENT_PR:
350 	    case CODE_UNREADABLE_PR:
351 	    case CODE_LOCKED_PR:
352 	    case CODE_FILE_ERROR:
353 	    case CODE_ERROR:
354 	    case CODE_NO_ADM_ENTRY:
355 	    case CODE_INVALID_FIELD_NAME:
356 	    case CODE_INVALID_PR_CONTENTS:
357 	    case CODE_INVALID_ENUM:
358 	    case CODE_INVALID_DATE:
359 	    case CODE_INVALID_EXPR:
360 	    case CODE_INVALID_DATABASE:
361 	    case CODE_INVALID_FIELD_EDIT:
362 	      fprintf (stderr, "%s: %s\n", program_name, r->text);
363 	      safe_exit ();
364 	      break;
365 
366 	    case CODE_PR_NOT_LOCKED:
367 	      fprintf (stderr, "%s: PR is not locked\n", program_name);
368 	      safe_exit ();
369 	      break;
370 
371 	    case CODE_GNATS_LOCKED:
372 	      fprintf (stderr, "%s: database is locked\n", program_name);
373 	      safe_exit ();
374 	      break;
375 
376 	    case CODE_NO_PRS_MATCHED:
377 	      fprintf (stderr, "%s: no PRs matched\n", program_name);
378 	      safe_exit ();
379 	      break;
380 
381 	    case CODE_NO_KERBEROS:
382 	      fprintf (stderr,
383 		       "%s: no Kerberos support, authentication failed\n",
384 		       program_name);
385 	      /* FALLTHROUGH */
386 	    case CODE_NO_ACCESS:
387 #ifdef HAVE_KERBEROS
388 	      if (remember_krb_auth_ret)
389 		fprintf (stderr, "%s: error (%s) while attempting krb4-auth\n",
390 			 program_name,
391 			 krb_get_err_text (remember_krb_auth_ret));
392 	      else
393 		fprintf (stderr, "%s: access denied\n", program_name);
394 #else
395 	      fprintf (stderr, "%s: access denied\n", program_name);
396 #endif
397 	      safe_exit ();
398 	      break;
399 
400 	    default:
401 	      fprintf (stderr, "%s: cannot understand %d `%s'\n",
402 		       program_name, r->state, r->text);
403 	      safe_exit ();
404 	      break;
405 	    }
406 	  retval = r->state;
407 
408 	  free_reply (r);
409 	}
410       else
411 	{
412 	  if (debug)
413 	    {
414 	      fprintf (stderr, "%s: null reply from the server\n",
415 		       program_name);
416 	    }
417 	  retval = 0;
418 	  done = 1;
419 	}
420     }
421 
422   return retval;
423 }
424 
425 void
safe_exit(void)426 safe_exit (void)
427 {
428   static int doing_exit = 0;
429 
430   if (! doing_exit)
431     {
432       doing_exit = 1;
433       client_exit ();
434       exit (1);
435     }
436 }
437 
438 #ifdef HAVE_KERBEROS
439 
440 void
tohex(char * ptr,char * buf,int sz)441 tohex (char *ptr, char *buf, int sz)
442 {
443   static const char hex[] = "0123456789abcdef";
444   int c;
445   while (sz--)
446     {
447       c = *ptr++ & 0xff;
448       *buf++ = hex[c >> 4];
449       *buf++ = hex[c & 15];
450     }
451   *buf++ = 0;
452 }
453 
454 int
hexdigit(char c)455 hexdigit (char c)
456 {
457   if (c >= '0' && c <= '9')
458     return c - '0';
459   if (c >= 'a' && c <= 'f')
460     return c - 'a' + 10;
461   if (c >= 'A' && c <= 'F')
462     return c - 'A' + 10;
463   return -1;
464 }
465 
466 void
fromhex(char * ptr,char * buf,int sz)467 fromhex (char *ptr, char *buf, int sz)
468 {
469   int val;
470   while (sz--)
471     {
472       val = hexdigit (*buf++);
473       val <<= 4;
474       val |= hexdigit (*buf++);
475       *ptr++ = val;
476     }
477 }
478 #endif
479 
480 static int
clientConnect(ErrorDesc * err,const char * hostname,int port)481 clientConnect (ErrorDesc *err, const char *hostname, int port)
482 {
483   FILE *file;
484 
485 #if 1
486   if (hostname[0] == '/' || hostname[0] == '.')
487     {
488       int sockets[2];
489 
490       setuid (getuid ());
491       setgid (getgid ());
492       socketpair (PF_UNIX, SOCK_STREAM, 0, sockets);
493 
494       forkedGnatsd = fork ();
495       if (forkedGnatsd == 0)
496 	{
497 	  unsetenv ("GNATSDB");
498 	  close (0);
499 	  close (1);
500 	  dup (sockets[0]);
501 	  dup (sockets[0]);
502 	  execl (hostname, hostname, "--not-inetd", NULL);
503 	  _exit (1);
504 	}
505       else
506 	{
507 	  sockfd = sockets[1];
508 	}
509     }
510   else
511 #endif
512     {
513       struct sockaddr_in server;
514       struct hostent *host;
515 
516       memset ((char *) &server, 0, sizeof (server));
517       server.sin_family = AF_INET;
518       if (isdigit ((int) hostname[0]))
519 	{
520 	  server.sin_addr.s_addr = inet_addr (hostname);
521 	}
522       else
523 	{
524 	  host = gethostbyname (hostname);
525 	  if (host == NULL)
526 	    {
527 	      setError (err, CODE_INVALID_DATABASE, "%s: No such host",
528 			hostname);
529 	      return -1;
530 	    }
531 	  memcpy (&server.sin_addr, host->h_addr, sizeof (server.sin_addr));
532 	}
533 
534       server.sin_port = htons (port);
535 
536       if (debug)
537 	{
538 	  fprintf (stderr, "opening connection to %s\n", hostname);
539 	}
540 
541       sockfd = socket (AF_INET, SOCK_STREAM, 0);
542       if (sockfd < 0)
543 	{
544 	  setError (err, CODE_ERROR, "could not open a socket");
545 	  return -1;
546 	}
547       if (connect (sockfd, (struct sockaddr *) &server, sizeof (server)) < 0)
548 	{
549 	  setError (err, CODE_ERROR,
550 		    "Couldn't connect to server on %s, port %d",
551 		    hostname, port);
552 	  close (sockfd);
553 	  return -1;
554 	}
555     }
556   if ((file = fdopen (sockfd, "r")) == NULL)
557     {
558       setError (err, CODE_ERROR, "read fdopen() failed");
559       close (sockfd);
560       return -1;
561     }
562   serv_read = file;
563   dupfd = dup (sockfd);
564   if (dupfd < 0)
565     {
566       setError (err, CODE_ERROR, "dup() failed");
567       close (sockfd);
568       return -1;
569     }
570   if ((file = fdopen (dupfd, "w")) == NULL)
571     {
572       setError (err, CODE_ERROR, "write fdopen() failed");
573       close (sockfd);
574       close (dupfd);
575       return -1;
576     }
577   serv_write = file;
578 
579   /* Get the hello.  */
580   get_reply (stdout);
581 
582 #ifdef HAVE_KERBEROS
583   {
584     /* This is ugly, because krb_sendauth/krb_recvauth don't maintain
585        a sensible connection state if authentication fails.  This is
586        unacceptable here.  So I've broken out most of the interesting
587        bits of those routines here.  I've also added a hex encoding of
588        the data exchanged instead of the standard authenticator
589        format, to maintain the text-only protocol used in GNATS.  */
590 
591     Reply *r;
592     char *realm, *p, *me;
593     int ret, i;
594     struct sockaddr_in laddr;
595     int laddrlen = sizeof (laddr);
596     char hname[MAXHOSTNAMELEN]; /* for canonicalization by krb_mk_auth */
597     /* Scratch space for Kerberos library, required to be provided by
598        the application.  Keep it all together so we can erase it all
599        at once later.  */
600     struct {
601       CREDENTIALS cred;
602       MSG_DAT msg_data;
603       Key_schedule sched;
604       KTEXT_ST tkt, packet;
605       char buf[MAX_KTXT_LEN * 2 + 10];
606     } k;
607     k.packet.mbz = 0;
608 
609 #define PUNT(reason,subreason)	do{if (debug) fprintf(stderr,"%s: %s (%s)\n", program_name, reason, subreason);goto krb_exit;}while(0)
610 #define SKIP(reason)		PUNT("skipping Kerberos authentication",reason)
611 #define FAIL(reason)		PUNT("Kerberos authentication failed",  reason)
612 
613     /* This probably ought to be a crypto checksum of the
614        authenticator, but a constant should be nearly as secure.  */
615 #define CKSUM			0x10291966
616 
617     strcpy (hname, host->h_name);
618 
619     realm = krb_realmofhost (hname);
620 
621     ret = krb_mk_auth (KOPT_DO_MUTUAL, &k.tkt,
622 		       GNATS_KRB4_PRINCIPAL_NAME, hname, realm,
623 		       CKSUM, GNATS_KRB4_VERSIONID, &k.packet);
624     remember_krb_auth_ret = ret;
625     if (ret)
626       SKIP (krb_get_err_text (ret));
627 
628     ret = krb_get_cred (GNATS_KRB4_PRINCIPAL_NAME, hname, realm, &k.cred);
629     /* This should always work unless the ticket file has been changed
630        out from under the client at just the wrong time.  */
631     if (ret)
632       abort ();
633 
634     /* Since we don't currently have an interface for specifying a
635        "local name" on the server distinct from the Kerberos principal
636        name, just use the principal.  */
637     me = k.cred.pname;
638 
639     if (debug)
640       {
641 	fprintf (stderr, "%s: writing `AUTH krb4-simple'\n", program_name);
642       }
643     fprintf (serv_write, "AUTH krb4-simple\r\n");
644     fflush (serv_write);
645     r = server_reply ();
646     if (!r || r->state == CODE_AUTH_TYPE_UNSUP)
647       {
648 	SKIP ("not supported by server");
649       }
650     else if (r->state == CODE_ERROR)
651       {
652 	fprintf (stderr, "%s: server authentication error: %s\n",
653 		 program_name, r->text);
654 	goto krb_exit;
655       }
656     else if (r->state != CODE_OK)
657       {
658 	abort ();
659       }
660     else
661       {
662 	free_reply (r);
663       }
664 
665     if (getsockname (sockfd, (struct sockaddr *) &laddr, &laddrlen) < 0)
666       {
667 	fprintf (stderr, "%s: getsockname: %s", program_name,
668 		 strerror (errno));
669 	exit (1);
670       }
671 
672     k.buf[0] = 'x';
673     /* This would be better with a base-64 encoding, but I'm too lazy
674        to write it write now.  */
675     tohex (k.packet.dat, k.buf + 1, k.packet.length);
676 
677     fprintf (serv_write, "%s\r\n%s\r\n", me, k.buf);
678     fflush (serv_write);
679 
680     i = fgetc (serv_read);
681     if (i == 'x')
682       /* ok */
683       k.buf[0] = i;
684     else
685       {
686 	/* error return */
687 	ungetc (i, serv_read);
688 	r = server_reply ();
689 	fprintf (stderr, "%s: %s\n", program_name, r->text);
690 	goto krb_exit;
691       }
692     p = fgets (k.buf + 1, sizeof (k.buf) - 1, serv_read);
693     if (!p)
694       /* eof */
695       goto krb_exit;
696     p = strchr (k.buf + 1, '\r');
697     if (p)
698       *p = 0;
699     k.packet.length = (strlen (k.buf) - 1) / 2;
700     fromhex (k.packet.dat, k.buf + 1, k.packet.length);
701     ret = krb_check_auth (&k.packet, CKSUM, &k.msg_data,
702 			  k.cred.session, k.sched, &laddr, &server);
703     if (ret)
704       fprintf (stderr, "%s: authentication failed: %s\n",
705 	       program_name, krb_get_err_text (ret));
706 
707     /* Now that authentication has succeeded (presumably), get an "ok"
708        response if authorization succeeds, or an error if it fails.
709        (Not everyone listed in the Kerberos database is necessarily
710        permitted to retrieve information from GNATS.)  */
711     get_reply (stdout);
712 
713   krb_exit:
714     /* Zero out the Kerberos scratch area, since it includes
715        authentication information we don't want going into core files
716        etc.  */
717     memset ((char *) &k, '?', sizeof (k));
718   }
719 #endif
720   return 0;
721 }
722 
723 void
client_exit(void)724 client_exit (void)
725 {
726   /* Do nothing, if we're not a network client.  */
727   if (serv_read != NULL && serv_write != NULL)
728     {
729       /* That's it, say bye-bye.  */
730       if (debug)
731 	{
732 	  fprintf (stderr, "%s: writing `QUIT'\n", program_name);
733 	}
734       fflush (serv_read);
735       fprintf (serv_write, "QUIT\r\n");
736 
737       fclose (serv_write);
738       fclose (serv_read);
739       serv_write = NULL;
740       serv_read = NULL;
741     }
742   if (forkedGnatsd != 0)
743     {
744       int status;
745 
746       wait (&status);
747     }
748 }
749 
750 
751 /* change database command to recognize databse aliases for network
752    client commands like nquery-pr. */
753 void
client_chdb(const char * newRoot)754 client_chdb (const char *newRoot)
755 {
756   if (newRoot == NULL)
757     {
758       newRoot = "default";
759     }
760 
761   /* send the change db command and get server's reply */
762   if (debug)
763     {
764       fprintf (stderr, "%s: writing `CHDB %s'\n", program_name, newRoot);
765     }
766   fprintf (serv_write, "CHDB %s\r\n", newRoot);
767   get_reply (stdout);			/* this will exit on error */
768 }
769 
770 static void
client_user(const char * user,const char * passwd)771 client_user (const char *user, const char *passwd)
772 {
773   /* send the user command and get server's reply */
774   if (debug)
775     {
776       fprintf (stderr, "%s: writing `USER %s %s'\n", program_name,
777 	       user, passwd);
778     }
779   fprintf (serv_write, "USER %s %s\r\n", user, passwd);
780   get_reply(stdout);			/* this will exit on error */
781 }
782 
783 /* Scan the environment and the global database configuration file for the
784    info needed to connect to the gnatsd server.  */
785 void
scanEnv(char ** user,char ** passwd,char ** hostname,int * port,char ** database)786 scanEnv (char **user, char **passwd, char **hostname,
787 	 int *port, char **database)
788 {
789   char *vals[5];
790   char *evar = *database == NULL ? getenv ("GNATSDB") : *database;
791   bool is_net_conn = databaseSpecIsNetConn (evar);
792 
793   if (evar == NULL || evar[0] == '\0' || is_net_conn)
794     {
795       if (*hostname == NULL)
796 	{
797 	  const char *specS = databaseSpecServer (evar);
798 	  if (specS != NULL)
799 	    {
800 	      *hostname = xstrdup (specS);
801 	    }
802 	}
803       if (*port == -1)
804 	{
805 	  const char *pname = databaseSpecPort (evar);
806 
807 	  if (pname != NULL)
808 	    {
809 	      if (isdigit ((int) (pname[0])))
810 		{
811 		  *port = atoi (pname);
812 		}
813 	      else
814 		{
815 		  struct servent *s = getservbyname (pname, "tcp");
816 		  if (s != NULL)
817 		    {
818 		      *port = ntohs (s->s_port);
819 		    }
820 		}
821 	    }
822 	}
823     }
824   else
825     {
826       char *place = evar;
827       int x;
828 
829       for (x = 0; x < 5; x++)
830 	{
831 	  if (place != NULL)
832 	    {
833 	      char *nplace = strchr (place, ':');
834 	      if (nplace != NULL)
835 		{
836 		  vals[x] = xmalloc (nplace - place + 1);
837 		  memcpy (vals[x], place, nplace - place);
838 		  vals[x][nplace - place] = '\0';
839 		  place = nplace + 1;
840 		}
841 	      else
842 		{
843 		  vals[x] = xstrdup (place);
844 		  place = NULL;
845 		}
846 	    }
847 	  else
848 	    {
849 	      vals[x] = xstrdup ("");
850 	    }
851 	}
852 
853       if (*hostname == NULL)
854 	{
855 	  *hostname = vals[0];
856 	}
857       else
858 	{
859 	  free (vals[0]);
860 	}
861 
862       if (*port == -1 && vals[1][0] != '\0')
863 	{
864 	  *port = atoi (vals[1]);
865 	}
866 
867       free (vals[1]);
868       if (*database == NULL && vals[2][0] != '\0')
869 	{
870 	  *database = vals[2];
871 	}
872       else
873 	{
874 	  free (vals[2]);
875 	}
876 
877       if (*user == NULL && vals[3][0] != '\0')
878 	{
879 	  *user = vals[3];
880 	}
881       else
882 	{
883 	  free (vals[3]);
884 	}
885 
886       if (*passwd == NULL && vals[4][0] != '\0')
887 	{
888 	  *passwd = vals[4];
889 	}
890       else
891 	{
892 	  free (vals[4]);
893 	}
894     }
895 
896   if (*user == NULL)
897     {
898       /* This is just wrong, but we'll live with it. XXX ??? !!! */
899       char *lname = getlogin ();
900       if (lname != NULL)
901 	{
902 	  *user = xstrdup (lname);
903 	}
904       else
905 	{
906 	  struct passwd *p;
907 
908 	  p = getpwuid (getuid ());
909 	  if (p != NULL)
910 	    {
911 	      *user = xstrdup (p->pw_name);
912 	    }
913 	}
914     }
915 
916   if (*passwd == NULL)
917     {
918       *passwd = xstrdup ("*");
919     }
920 
921   if (*database == NULL && ! is_net_conn)
922     {
923       *database = ((evar == NULL || evar[0] == '\0')
924 		   ? xstrdup ("default")
925 		   : xstrdup (evar));
926     }
927 
928   if (*port == -1)
929     {
930       struct servent *s = getservbyname (DEFAULT_GNATS_SERVICE, "tcp");
931       if (s != NULL)
932 	{
933 	  *port = ntohs (s->s_port);
934 	}
935     }
936 }
937 
938 int
client_init_gnats(ErrorDesc * err,const char * userArg,const char * passwdArg,const char * hostnameArg,int port,const char * databaseArg)939 client_init_gnats (ErrorDesc *err, const char *userArg, const char *passwdArg,
940 		   const char *hostnameArg, int port, const char *databaseArg)
941 {
942   char *user = NULL;
943   char *passwd = NULL;
944   char *hostname = NULL;
945   char *database = NULL;
946   int res = -1;
947 
948   if (userArg != NULL)
949     {
950       user = xstrdup (userArg);
951     }
952 
953   if (passwdArg != NULL)
954     {
955       passwd = xstrdup (passwdArg);
956     }
957 
958   if (hostnameArg != NULL)
959     {
960       hostname = xstrdup (hostnameArg);
961     }
962 
963   if (databaseArg != NULL)
964     {
965       database = xstrdup (databaseArg);
966     }
967 
968   scanEnv (&user, &passwd, &hostname, &port, &database);
969 
970   if (hostname == NULL)
971     {
972       setError (err, CODE_INVALID_DATABASE,
973 		"Can't determine hostname to connect to");
974     }
975   else if (port < 0)
976     {
977       setError (err, CODE_INVALID_DATABASE,
978 		"Can't determine port number to connect to");
979     }
980   else if (user == NULL)
981     {
982       setError (err, CODE_INVALID_DATABASE,
983 		"Can't determine username to send to the server");
984     }
985   else if (passwd == NULL)
986     {
987       setError (err, CODE_INVALID_DATABASE,
988 		"Can't determine password to send to the server");
989     }
990   else if (clientConnect (err, hostname, port) == 0)
991     {
992       client_chdb (database);
993       client_user (user, passwd);
994       res = 0;
995     }
996 
997   if (user != NULL)
998     {
999       free (user);
1000     }
1001   if (passwd != NULL)
1002     {
1003       free (passwd);
1004     }
1005   if (hostname != NULL)
1006     {
1007       free (hostname);
1008     }
1009   if (database != NULL)
1010     {
1011       free (database);
1012     }
1013 
1014   return res;
1015 }
1016 
1017 static DatabaseInfo lockedDatabase = NULL;
1018 
1019 int
client_lock_gnats(const DatabaseInfo database,ErrorDesc * err)1020 client_lock_gnats (const DatabaseInfo database, ErrorDesc *err)
1021 {
1022   if (lockedDatabase != NULL)
1023     {
1024       abort ();
1025     }
1026   lockedDatabase = database;
1027   return lock_gnats (database, err);
1028 }
1029 
1030 void
client_unlock_gnats(void)1031 client_unlock_gnats (void)
1032 {
1033   ErrorDesc err;
1034   DatabaseInfo database = lockedDatabase;
1035   lockedDatabase = NULL;
1036 
1037   if (database != NULL)
1038     {
1039       if (unlock_gnats (database, &err) < 0)
1040 	{
1041 	  client_print_errors (database, err);
1042 	}
1043     }
1044 }
1045 
1046 void
client_print_errors(const DatabaseInfo database,ErrorDesc err)1047 client_print_errors (const DatabaseInfo database, ErrorDesc err)
1048 {
1049   switch (getErrorCode (err))
1050     {
1051     case CODE_NO_INDEX:
1052     case CODE_FILE_ERROR:
1053       fprintf (stderr, "%s: %s\n", program_name, getErrorMessage (err));
1054       punt (database, 1, getErrorMessage (err));
1055       break;
1056 
1057     case CODE_INVALID_FIELD_CONTENTS:
1058       {
1059 	BadFields badFieldList = getBadFieldList (err);
1060 	while (badFieldList != NULL)
1061 	  {
1062 	    fprintf (stderr, "%s: invalid value `%s' in field %s\n",
1063 		     program_name,
1064 		     badFieldValue (badFieldList),
1065 		     fieldDefForIndex (badFieldIndex (badFieldList))->name);
1066 	    badFieldList = nextBadField (badFieldList);
1067 	  }
1068       }
1069       break;
1070 
1071     case CODE_INVALID_ENUM:
1072       printf ("%s: invalid fields:\n", program_name);
1073       {
1074 	BadFields badFieldList = getBadFieldList (err);
1075 	while (badFieldList != NULL)
1076 	  {
1077 	    FieldIndex f = badFieldIndex (badFieldList);
1078 	    printf ("\tNote: There was a bad value `%s' for the field `%s'.\n\tIt was set to the default value of `%s'.\n",
1079 		    badFieldValue (badFieldList),
1080 		    fieldDefForIndex (f)->name,
1081 		    fieldDefForIndex (f)->default_value);
1082 	    badFieldList = nextBadField (badFieldList);
1083 	  }
1084       }
1085       break;
1086 
1087     default:
1088       fprintf (stderr, "%s: %s\n", program_name, getErrorMessage (err));
1089       break;
1090     }
1091 }
1092 
1093 void
sendRemoteListQuery(ListTypes whichList,FILE * outfile)1094 sendRemoteListQuery (ListTypes whichList, FILE *outfile)
1095 {
1096   const char *listName = listTypeToString (whichList);
1097   if (listName != NULL)
1098     {
1099       if (debug)
1100 	{
1101 	  fprintf (stderr, "%s: writing `LIST %s'\n",
1102 		   program_name, listName);
1103 	}
1104       fprintf (serv_write, "LIST %s\r\n", listName);
1105       if (outfile != NULL)
1106 	{
1107 	  get_reply (outfile);
1108 	}
1109     }
1110 }
1111 
1112 static void
sendQueryFormat(const char * name)1113 sendQueryFormat (const char *name)
1114 {
1115   if (debug)
1116     {
1117       fprintf (stderr, "%s: writing `QFMT %s'\n", program_name, name);
1118     }
1119   fprintf (serv_write, "QFMT %s\r\n", name);
1120   get_reply (NULL);
1121 }
1122 
1123 void
sendRemoteQuery(const char * queryString,const char ** args,const char * query_format_name,FILE * outfile)1124 sendRemoteQuery (const char *queryString, const char **args,
1125 		 const char *query_format_name, FILE *outfile)
1126 {
1127   char *buf;
1128   int i = 0;
1129   size_t buflen;
1130 
1131   if (queryString != NULL && queryString[0] != '\0')
1132     {
1133       if (debug)
1134 	{
1135 	  fprintf (stderr, "%s: writing `EXPR %s'\n", program_name,
1136 		   queryString);
1137 	}
1138       fprintf (serv_write, "EXPR %s\r\n", queryString);
1139       get_reply (NULL);
1140     }
1141 
1142   sendQueryFormat (query_format_name);
1143 
1144   /* I'm not entirely sure why we go through this foolishness of
1145      building up a string containing the command, only to just
1146      print it out--why not call fprintf() a bunch of times?  It'd
1147      probably be faster.  XXX ??? !!! */
1148   buf = xstrdup ("QUER");
1149   buflen = 4;
1150 
1151   while (args != NULL && args[i] != NULL)
1152     {
1153       size_t arglen = strlen (args[i]);
1154 
1155       buf = xrealloc (buf, buflen + arglen + 2);
1156       buf[buflen] = ' ';
1157       memcpy (buf + buflen + 1, args[i], arglen + 1);
1158       buflen += arglen + 1;
1159       i++;
1160     }
1161 
1162   if (debug)
1163     {
1164       fprintf (stderr, "%s: writing `%s'\n", program_name, buf);
1165     }
1166   fprintf (serv_write, "%s\r\n", buf);
1167   free (buf);
1168 
1169   if (outfile != NULL)
1170     {
1171       get_reply (outfile);
1172     }
1173 }
1174 
1175 void
clientGetAdmField(FILE * outfile,const char * admField,const char * admSubfield,const char * admKey)1176 clientGetAdmField (FILE *outfile, const char *admField,
1177 		   const char *admSubfield, const char *admKey)
1178 {
1179   if (admSubfield == NULL)
1180     {
1181       admSubfield = "";
1182     }
1183   fprintf (serv_write, "ADMV %s %s %s\r\n", admField, admKey, admSubfield);
1184   get_reply (outfile);
1185 }
1186 
1187 /* Read PR PRNUM from the GNATS server, and return it.  */
1188 PR *
clientReadPR(const char * prNum)1189 clientReadPR (const char *prNum)
1190 {
1191   PR *res;
1192 
1193   prBeingRead = allocPR (NULL);
1194   fprintf (serv_write, "RSET\r\n");
1195   get_reply (NULL);
1196   fprintf (serv_write, "QFMT full\r\n");
1197   get_reply (NULL);
1198   fprintf (serv_write, "QUER %s\r\n", prNum);
1199   initReadPR (prBeingRead);
1200   get_reply (NULL);
1201   res = prBeingRead;
1202   prBeingRead = NULL;
1203   return res;
1204 }
1205 
1206 /* Return a list of PRs that match the specified query expression. */
1207 StringList *
clientGetPRList(const char * expr)1208 clientGetPRList (const char *expr)
1209 {
1210   StringList *res = NULL;
1211 
1212   fprintf (serv_write, "RSET\r\n");
1213   get_reply (NULL);
1214   sendRemoteQuery (expr, NULL, "Number", NULL);
1215   prList = &res;
1216   get_reply (NULL);
1217   return res;
1218 }
1219 
1220 void
netSetEditEmailAddr(const char * addr)1221 netSetEditEmailAddr (const char *addr)
1222 {
1223   fprintf (serv_write, "EDITADDR %s\r\n", addr);
1224   get_reply (NULL);
1225 }
1226 
1227 static void
netSendPRCmd(const char * cmd,FILE * file,int show_information)1228 netSendPRCmd (const char *cmd, FILE *file, int show_information)
1229 {
1230   char *line;
1231   if (debug)
1232     {
1233       fprintf (stderr, "%s: writing `%s'\n", program_name, cmd);
1234     }
1235   fprintf (serv_write, "%s\r\n", cmd);
1236   get_reply (stdout); /* XXX */
1237 
1238   while ((line = read_line (file, NULL)) != NULL)
1239     {
1240       write_multitext (serv_write, line, "\r\n");
1241       free (line);
1242     }
1243   fprintf (serv_write, ".\r\n");
1244   /* if get_reply finds errors it writes messages and doesn't return */
1245   get_reply (show_information ? stdout : NULL);
1246 }
1247 
1248 void
netCheckPR(FILE * file,int initial)1249 netCheckPR (FILE *file, int initial)
1250 {
1251   netSendPRCmd (initial ? "CHEK INIT" : "CHEK", file, 1);
1252 }
1253 
1254 void
netEditField(FILE * fp,const char * prnum,const char * fieldname,const char * editEmailAddr,int append,char * reason)1255 netEditField (FILE *fp, const char *prnum, const char *fieldname,
1256 	      const char *editEmailAddr, int append, char *reason)
1257 {
1258   const char *cmd;
1259   char *line;
1260   int reply;
1261 
1262   netSetEditEmailAddr (editEmailAddr);
1263   if (append)
1264     {
1265       cmd = "APPN";
1266     }
1267   else
1268     {
1269       cmd = "REPL";
1270     }
1271   fprintf (serv_write, "%s %s %s\r\n", cmd, prnum, fieldname);
1272 
1273   do {
1274     reply = get_reply (stdout);
1275     switch (reply)
1276       {
1277         case CODE_SEND_TEXT:
1278           while ((line = read_line (fp, NULL)) != NULL)
1279             {
1280               write_multitext (serv_write, line, "\r\n");
1281               free (line);
1282             }
1283           fprintf (serv_write, ".\r\n");
1284 	  break;
1285 
1286         case CODE_SEND_CHANGE_REASON:
1287           if (reason)
1288 	    fprintf (serv_write, "%s\r\n.\r\n", reason);
1289           else
1290 	    {
1291 	      fprintf (stderr, "%s: server wants a change-reason for %s,"
1292 	         " but none was supplied.\n", program_name, fieldname);
1293 	      safe_exit ();
1294 	    }
1295 	  break;
1296 
1297 	case CODE_OK:
1298 	  break;
1299 
1300         default:
1301 	  fprintf (stderr, "%s: bad reply from server: %d.\n",
1302 		   program_name, reply);
1303 	  safe_exit ();
1304 	  break;
1305       }
1306   } while (reply != CODE_OK);
1307 }
1308 
1309 void
netSubmitNewPR(FILE * file,int show_prnum)1310 netSubmitNewPR (FILE *file, int show_prnum)
1311 {
1312   netSendPRCmd ("SUBM", file, show_prnum);
1313 }
1314 
1315 void
netModifyPR(FILE * file,const char * prNum,const char * editEmailAddr)1316 netModifyPR (FILE *file, const char *prNum, const char *editEmailAddr)
1317 {
1318   char *buf;
1319 
1320   netSetEditEmailAddr (editEmailAddr);
1321   asprintf (&buf, "EDIT %s", prNum);
1322   netSendPRCmd (buf, file, 1);
1323   free (buf);
1324 }
1325 
1326 void
netLockDB(void)1327 netLockDB (void)
1328 {
1329   if (debug)
1330     {
1331       fprintf (stderr, "%s: writing `LKDB'\n", program_name);
1332     }
1333   fprintf (serv_write, "LKDB\r\n");
1334   get_reply (stdout);
1335 }
1336 
1337 void
netUnlockDB(void)1338 netUnlockDB (void)
1339 {
1340   if (debug)
1341     {
1342       fprintf (stderr, "%s: writing `UNDB'\n", program_name);
1343     }
1344   fprintf (serv_write, "UNDB\r\n");
1345   get_reply (stdout);
1346 }
1347 
1348 void
netLockPR(const char * prNum,const char * username,const char * processID)1349 netLockPR (const char *prNum, const char *username, const char *processID)
1350 {
1351   if (processID != NULL)
1352     {
1353       fprintf (serv_write, "LOCK %s %s %s\r\n", prNum, username, processID);
1354     }
1355   else
1356     {
1357       fprintf (serv_write, "LOCK %s %s\r\n", prNum, username);
1358     }
1359   get_reply (stdout);
1360 }
1361 
1362 void
netUnlockPR(const char * prNum)1363 netUnlockPR (const char *prNum)
1364 {
1365   if (debug)
1366     {
1367       fprintf (stderr, "%s: writing `UNLK %s'\n", program_name, prNum);
1368     }
1369   fprintf (serv_write, "UNLK %s\r\n", prNum);
1370   get_reply (stdout);
1371 }
1372 
1373 void
netDeletePR(const char * prNum,const char * editUserEmailAddr)1374 netDeletePR (const char *prNum, const char *editUserEmailAddr)
1375 {
1376   netSetEditEmailAddr (editUserEmailAddr);
1377   if (debug)
1378     {
1379       fprintf (stderr, "%s: writing `DELETE %s'\n", program_name, prNum);
1380     }
1381   fprintf (serv_write, "DELETE %s\r\n", prNum);
1382   get_reply (stdout);
1383 }
1384 
1385 void
netFieldFlags(const char * fieldname)1386 netFieldFlags (const char *fieldname)
1387 {
1388   fprintf (serv_write, "FIELDFLAGS %s\r\n", fieldname);
1389   get_reply (stdout);
1390 }
1391 
1392 void
netValidValues(const char * fieldname)1393 netValidValues (const char *fieldname)
1394 {
1395   fprintf (serv_write, "FVLD %s\r\n", fieldname);
1396   get_reply (stdout);
1397 }
1398 
1399 void
netFieldDescription(const char * fieldname)1400 netFieldDescription (const char *fieldname)
1401 {
1402   fprintf (serv_write, "FDSC %s\r\n", fieldname);
1403   get_reply (stdout);
1404 }
1405 
1406 void
netFieldType(const char * fieldname)1407 netFieldType (const char *fieldname)
1408 {
1409   fprintf (serv_write, "FTYP %s\r\n", fieldname);
1410   get_reply (stdout);
1411 }
1412