1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29/* Copyright (c) 1981 Regents of the University of California */
30
31#include "ex.h"
32#include "ex_argv.h"
33#include "ex_temp.h"
34#include "ex_tty.h"
35#include <stdlib.h>
36#include <locale.h>
37#include <stdio.h>
38#ifdef TRACE
39unsigned char	tttrace[BUFSIZ];
40#endif
41
42#define	EQ(a, b)	(strcmp(a, b) == 0)
43
44char	*strrchr();
45void	init_re(void);
46
47/*
48 * The code for ex is divided as follows:
49 *
50 * ex.c			Entry point and routines handling interrupt, hangup
51 *			signals; initialization code.
52 *
53 * ex_addr.c		Address parsing routines for command mode decoding.
54 *			Routines to set and check address ranges on commands.
55 *
56 * ex_cmds.c		Command mode command decoding.
57 *
58 * ex_cmds2.c		Subroutines for command decoding and processing of
59 *			file names in the argument list.  Routines to print
60 *			messages and reset state when errors occur.
61 *
62 * ex_cmdsub.c		Subroutines which implement command mode functions
63 *			such as append, delete, join.
64 *
65 * ex_data.c		Initialization of options.
66 *
67 * ex_get.c		Command mode input routines.
68 *
69 * ex_io.c		General input/output processing: file i/o, unix
70 *			escapes, filtering, source commands, preserving
71 *			and recovering.
72 *
73 * ex_put.c		Terminal driving and optimizing routines for low-level
74 *			output (cursor-positioning); output line formatting
75 *			routines.
76 *
77 * ex_re.c		Global commands, substitute, regular expression
78 *			compilation and execution.
79 *
80 * ex_set.c		The set command.
81 *
82 * ex_subr.c		Loads of miscellaneous subroutines.
83 *
84 * ex_temp.c		Editor buffer routines for main buffer and also
85 *			for named buffers (Q registers if you will.)
86 *
87 * ex_tty.c		Terminal dependent initializations from termcap
88 *			data base, grabbing of tty modes (at beginning
89 *			and after escapes).
90 *
91 * ex_unix.c		Routines for the ! command and its variations.
92 *
93 * ex_v*.c		Visual/open mode routines... see ex_v.c for a
94 *			guide to the overall organization.
95 */
96
97/*
98 * This sets the Version of ex/vi for both the exstrings file and
99 * the version command (":ver").
100 */
101
102/* variable used by ":ver" command */
103unsigned char *Version = (unsigned char *)"Version SVR4.0, Solaris 2.5.0";
104
105/*
106 * NOTE: when changing the Version number, it must be changed in the
107 *	 following files:
108 *
109 *			  port/READ_ME
110 *			  port/ex.c
111 *			  port/ex.news
112 *
113 */
114#ifdef XPG4
115unsigned char *savepat = (unsigned char *) NULL; /* firstpat storage	*/
116#endif /* XPG4 */
117
118/*
119 * Main procedure.  Process arguments and then
120 * transfer control to the main command processing loop
121 * in the routine commands.  We are entered as either "ex", "edit", "vi"
122 * or "view" and the distinction is made here. For edit we just diddle options;
123 * for vi we actually force an early visual command.
124 */
125static unsigned char cryptkey[19]; /* contents of encryption key */
126
127static void usage(unsigned char *);
128
129static int validate_exrc(unsigned char *);
130
131void init(void);
132
133int
134main(int ac, char *av[])
135{
136	extern 	char 	*optarg;
137	extern 	int	optind;
138	unsigned char	*rcvname = 0;
139	unsigned char *cp;
140	int c;
141	unsigned char	*cmdnam;
142	bool recov = 0;
143	bool ivis = 0;
144	bool itag = 0;
145	bool fast = 0;
146	extern int verbose;
147	int argcounter = 0;
148	extern int tags_flag;	/* Set if tag file is not sorted (-S flag) */
149	unsigned char scratch [PATH_MAX+1];   /* temp for sourcing rc file(s) */
150	int vret = 0;
151	unsigned char exrcpath [PATH_MAX+1]; /* temp for sourcing rc file(s) */
152	int toptseen = 0;
153#ifdef TRACE
154	unsigned char *tracef;
155#endif
156	tagflg = 0;
157	(void) setlocale(LC_ALL, "");
158#if !defined(TEXT_DOMAIN)
159#define	TEXT_DOMAIN "SYS_TEST"
160#endif
161	(void) textdomain(TEXT_DOMAIN);
162
163	/*
164	 * Immediately grab the tty modes so that we won't
165	 * get messed up if an interrupt comes in quickly.
166	 */
167	(void) gTTY(2);
168	normf = tty;
169	ppid = getpid();
170	/* Note - this will core dump if you didn't -DSINGLE in CFLAGS */
171	lines = 24;
172	columns = 80;	/* until defined right by setupterm */
173	/*
174	 * Defend against d's, v's, w's, and a's in directories of
175	 * path leading to our true name.
176	 */
177	if ((cmdnam = (unsigned char *)strrchr(av[0], '/')) != 0)
178		cmdnam++;
179	else
180		cmdnam = (unsigned char *)av[0];
181
182	if (EQ((char *)cmdnam, "vi"))
183		ivis = 1;
184	else if (EQ(cmdnam, "view")) {
185		ivis = 1;
186		value(vi_READONLY) = 1;
187	} else if (EQ(cmdnam, "vedit")) {
188		ivis = 1;
189		value(vi_NOVICE) = 1;
190		value(vi_REPORT) = 1;
191		value(vi_MAGIC) = 0;
192		value(vi_SHOWMODE) = 1;
193	} else if (EQ(cmdnam, "edit")) {
194		value(vi_NOVICE) = 1;
195		value(vi_REPORT) = 1;
196		value(vi_MAGIC) = 0;
197		value(vi_SHOWMODE) = 1;
198	}
199
200#ifdef	XPG4
201	{
202		struct winsize jwin;
203		char *envptr;
204
205		envlines = envcolumns = -1;
206		oldlines = oldcolumns = -1;
207
208		if (ioctl(0, TIOCGWINSZ, &jwin) != -1) {
209			oldlines = jwin.ws_row;
210			oldcolumns = jwin.ws_col;
211		}
212
213		if ((envptr = getenv("LINES")) != NULL &&
214		    *envptr != '\0' && isdigit(*envptr)) {
215			if ((envlines = atoi(envptr)) <= 0) {
216				envlines = -1;
217			}
218		}
219
220		if ((envptr = getenv("COLUMNS")) != NULL &&
221		    *envptr != '\0' && isdigit(*envptr)) {
222			if ((envcolumns = atoi(envptr)) <= 0) {
223				envcolumns = -1;
224			}
225		}
226	}
227#endif /* XPG4 */
228
229	draino();
230	pstop();
231
232	/*
233	 * Initialize interrupt handling.
234	 */
235	oldhup = signal(SIGHUP, SIG_IGN);
236	if (oldhup == SIG_DFL)
237		signal(SIGHUP, onhup);
238	oldquit = signal(SIGQUIT, SIG_IGN);
239	ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL;
240	if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
241		signal(SIGTERM, onhup);
242	signal(SIGILL, oncore);
243	signal(SIGTRAP, oncore);
244	signal(SIGIOT, oncore);
245	signal(SIGFPE, oncore);
246	signal(SIGBUS, oncore);
247	signal(SIGSEGV, oncore);
248	signal(SIGPIPE, oncore);
249	init_re();
250	while (1) {
251#ifdef TRACE
252		while ((c = getopt(ac, (char **)av, "VU:Lc:Tvt:rlw:xRCsS")) !=
253		    EOF)
254#else
255		while ((c = getopt(ac, (char **)av,
256		    "VLc:vt:rlw:xRCsS")) != EOF)
257#endif
258			switch (c) {
259			case 's':
260				hush = 1;
261				value(vi_AUTOPRINT) = 0;
262				fast++;
263				break;
264
265			case 'R':
266				value(vi_READONLY) = 1;
267				break;
268			case 'S':
269				tags_flag = 1;
270				break;
271#ifdef TRACE
272			case 'T':
273				tracef = (unsigned char *)"trace";
274				goto trace;
275
276			case 'U':
277				tracef = tttrace;
278				strcpy(tracef, optarg);
279		trace:
280				trace = fopen((char *)tracef, "w");
281#define	tracbuf	NULL
282				if (trace == NULL)
283					viprintf("Trace create error\n");
284				else
285					setbuf(trace, (char *)tracbuf);
286				break;
287#endif
288			case 'c':
289				if (optarg != NULL)
290					firstpat = (unsigned char *)optarg;
291				else
292					firstpat = (unsigned char *)"";
293				break;
294
295			case 'l':
296				value(vi_LISP) = 1;
297				value(vi_SHOWMATCH) = 1;
298				break;
299
300			case 'r':
301				if (av[optind] && (c = av[optind][0]) &&
302				    c != '-') {
303					if ((strlen(av[optind])) >=
304					    sizeof (savedfile)) {
305						(void) fprintf(stderr, gettext(
306						    "Recovered file name"
307						    " too long\n"));
308						exit(1);
309					}
310
311					rcvname = (unsigned char *)av[optind];
312					optind++;
313				}
314				/* FALLTHROUGH */
315
316			case 'L':
317				recov++;
318				break;
319
320			case 'V':
321				verbose = 1;
322				break;
323
324			case 't':
325				if (toptseen) {
326					usage(cmdnam);
327					exit(1);
328				} else {
329					toptseen++;
330				}
331				itag = tagflg = 1; /* -t option */
332				if (strlcpy(lasttag, optarg,
333				    sizeof (lasttag)) >= sizeof (lasttag)) {
334					(void) fprintf(stderr, gettext("Tag"
335					    " file name too long\n"));
336					exit(1);
337				}
338				break;
339
340			case 'w':
341				defwind = 0;
342				if (optarg[0] == NULL)
343					defwind = 3;
344				else for (cp = (unsigned char *)optarg;
345				    isdigit(*cp); cp++)
346					defwind = 10*defwind + *cp - '0';
347				if (defwind < 0)
348					defwind = 3;
349				break;
350
351			case 'C':
352				crflag = 1;
353				xflag = 1;
354				break;
355
356			case 'x':
357				/* encrypted mode */
358				xflag = 1;
359				crflag = -1;
360				break;
361
362			case 'v':
363				ivis = 1;
364				break;
365
366			default:
367				usage(cmdnam);
368				exit(1);
369			}
370		if (av[optind] && av[optind][0] == '+' &&
371		    av[optind-1] && strcmp(av[optind-1], "--")) {
372				firstpat = (unsigned char *)&av[optind][1];
373				optind++;
374				continue;
375		} else if (av[optind] && av[optind][0] == '-' &&
376		    av[optind-1] && strcmp(av[optind-1], "--")) {
377			hush = 1;
378			value(vi_AUTOPRINT) = 0;
379			fast++;
380			optind++;
381			continue;
382		}
383		break;
384	}
385
386	if (isatty(0) == 0) {
387		/*
388		 * If -V option is set and input is coming in via
389		 * stdin then vi behavior should be ignored. The vi
390		 * command should act like ex and only process ex commands
391		 * and echo the input ex commands to stderr
392		 */
393		if (verbose == 1) {
394			ivis = 0;
395		}
396
397		/*
398		 * If the standard input is not a terminal device,
399		 * it is as if the -s option has been specified.
400		 */
401		if (ivis == 0) {
402			hush = 1;
403			value(vi_AUTOPRINT) = 0;
404			fast++;
405		}
406	}
407
408	ac -= optind;
409	av  = &av[optind];
410
411	for (argcounter = 0; argcounter < ac; argcounter++) {
412		if ((strlen(av[argcounter])) >= sizeof (savedfile)) {
413			(void) fprintf(stderr, gettext("File argument"
414			    " too long\n"));
415			exit(1);
416		}
417	}
418
419#ifdef SIGTSTP
420	if (!hush && signal(SIGTSTP, SIG_IGN) == SIG_DFL)
421		signal(SIGTSTP, onsusp), dosusp++;
422#endif
423
424	if (xflag) {
425		permflag = 1;
426		if ((kflag = run_setkey(perm,
427		    (key = (unsigned char *)getpass(
428		    gettext("Enter key:"))))) == -1) {
429			kflag = 0;
430			xflag = 0;
431			smerror(gettext("Encryption facility not available\n"));
432		}
433		if (kflag == 0)
434			crflag = 0;
435		else {
436			strcpy(cryptkey, "CrYpTkEy=XXXXXXXXX");
437			strcpy(cryptkey + 9, key);
438			if (putenv((char *)cryptkey) != 0)
439			smerror(gettext(" Cannot copy key to environment"));
440		}
441
442	}
443#ifndef PRESUNEUC
444	/*
445	 * Perform locale-specific initialization
446	 */
447	localize();
448#endif /* PRESUNEUC */
449
450	/*
451	 * Initialize end of core pointers.
452	 * Normally we avoid breaking back to fendcore after each
453	 * file since this can be expensive (much core-core copying).
454	 * If your system can scatter load processes you could do
455	 * this as ed does, saving a little core, but it will probably
456	 * not often make much difference.
457	 */
458	fendcore = (line *) sbrk(0);
459	endcore = fendcore - 2;
460
461	/*
462	 * If we are doing a recover and no filename
463	 * was given, then execute an exrecover command with
464	 * the -r option to type out the list of saved file names.
465	 * Otherwise set the remembered file name to the first argument
466	 * file name so the "recover" initial command will find it.
467	 */
468	if (recov) {
469		if (ac == 0 && (rcvname == NULL || *rcvname == NULL)) {
470			ppid = 0;
471			setrupt();
472			execlp(EXRECOVER, "exrecover", "-r", (char *)0);
473			filioerr(EXRECOVER);
474			exit(++errcnt);
475		}
476		if (rcvname && *rcvname)
477			(void) strlcpy(savedfile, rcvname, sizeof (savedfile));
478		else {
479			(void) strlcpy(savedfile, *av++, sizeof (savedfile));
480			ac--;
481		}
482	}
483
484	/*
485	 * Initialize the argument list.
486	 */
487	argv0 = (unsigned char **)av;
488	argc0 = ac;
489	args0 = (unsigned char *)av[0];
490	erewind();
491
492	/*
493	 * Initialize a temporary file (buffer) and
494	 * set up terminal environment.  Read user startup commands.
495	 */
496	if (setexit() == 0) {
497		setrupt();
498		intty = isatty(0);
499		value(vi_PROMPT) = intty;
500		if (((cp = (unsigned char *)getenv("SHELL")) != NULL) &&
501		    (*cp != '\0')) {
502			if (strlen(cp) < sizeof (shell)) {
503				(void) strlcpy(shell, cp, sizeof (shell));
504			}
505		}
506		if (fast)
507			setterm((unsigned char *)"dumb");
508		else {
509			gettmode();
510			cp = (unsigned char *)getenv("TERM");
511			if (cp == NULL || *cp == '\0')
512				cp = (unsigned char *)"unknown";
513			setterm(cp);
514		}
515	}
516
517	/*
518	 * Bring up some code from init()
519	 * This is still done in init later. This
520	 * avoids null pointer problems
521	 */
522
523	dot = zero = truedol = unddol = dol = fendcore;
524	one = zero+1;
525	{
526	int i;
527
528	for (i = 0; i <= 'z'-'a'+1; i++)
529		names[i] = 1;
530	}
531
532	if (setexit() == 0 && !fast) {
533		if ((globp =
534		    (unsigned char *) getenv("EXINIT")) && *globp) {
535			if (ivis)
536				inexrc = 1;
537			commands(1, 1);
538			inexrc = 0;
539		} else {
540			globp = 0;
541			if ((cp = (unsigned char *) getenv("HOME")) !=
542			    0 && *cp) {
543				strncpy(scratch, cp, sizeof (scratch) - 1);
544				strncat(scratch, "/.exrc",
545				    sizeof (scratch) - 1 - strlen(scratch));
546				if (ivis)
547					inexrc = 1;
548				if ((vret = validate_exrc(scratch)) == 0) {
549					source(scratch, 1);
550				} else {
551					if (vret == -1) {
552						error(gettext(
553						    "Not owner of .exrc "
554						    "or .exrc is group or "
555						    "world writable"));
556					}
557				}
558				inexrc = 0;
559			}
560		}
561
562		/*
563		 * Allow local .exrc if the "exrc" option was set. This
564		 * loses if . is $HOME, but nobody should notice unless
565		 * they do stupid things like putting a version command
566		 * in .exrc.
567		 * Besides, they should be using EXINIT, not .exrc, right?
568		 */
569
570		if (value(vi_EXRC)) {
571			if (ivis)
572				inexrc = 1;
573			if ((cp = (unsigned char *) getenv("PWD")) != 0 &&
574			    *cp) {
575				strncpy(exrcpath, cp, sizeof (exrcpath) - 1);
576				strncat(exrcpath, "/.exrc",
577				    sizeof (exrcpath) - 1 - strlen(exrcpath));
578				if (strcmp(scratch, exrcpath) != 0) {
579					if ((vret =
580					    validate_exrc(exrcpath)) == 0) {
581						source(exrcpath, 1);
582					} else {
583						if (vret == -1) {
584							error(gettext(
585							    "Not owner of "
586							    ".exrc or .exrc "
587							    "is group or world "
588							    "writable"));
589						}
590					}
591				}
592			}
593			inexrc = 0;
594		}
595	}
596
597	init();	/* moved after prev 2 chunks to fix directory option */
598
599	/*
600	 * Initial processing.  Handle tag, recover, and file argument
601	 * implied next commands.  If going in as 'vi', then don't do
602	 * anything, just set initev so we will do it later (from within
603	 * visual).
604	 */
605	if (setexit() == 0) {
606		if (recov)
607			globp = (unsigned char *)"recover";
608		else if (itag) {
609			globp = ivis ? (unsigned char *)"tag" :
610			    (unsigned char *)"tag|p";
611#ifdef	XPG4
612			if (firstpat != NULL) {
613				/*
614				 * if the user specified the -t and -c
615				 * flags together, then we service these
616				 * commands here. -t is handled first.
617				 */
618				savepat = firstpat;
619				firstpat = NULL;
620				inglobal = 1;
621				commands(1, 1);
622
623				/* now handle the -c argument: */
624				globp = savepat;
625				commands(1, 1);
626				inglobal = 0;
627				globp = savepat = NULL;
628
629				/* the above isn't sufficient for ex mode: */
630				if (!ivis) {
631					setdot();
632					nonzero();
633					plines(addr1, addr2, 1);
634				}
635			}
636#endif /* XPG4 */
637		} else if (argc)
638			globp = (unsigned char *)"next";
639		if (ivis)
640			initev = globp;
641		else if (globp) {
642			inglobal = 1;
643			commands(1, 1);
644			inglobal = 0;
645		}
646	}
647
648	/*
649	 * Vi command... go into visual.
650	 */
651	if (ivis) {
652		/*
653		 * Don't have to be upward compatible
654		 * by starting editing at line $.
655		 */
656#ifdef	XPG4
657		if (!itag && (dol > zero))
658#else /* XPG4 */
659		if (dol > zero)
660#endif /* XPG4 */
661			dot = one;
662		globp = (unsigned char *)"visual";
663		if (setexit() == 0)
664			commands(1, 1);
665	}
666
667	/*
668	 * Clear out trash in state accumulated by startup,
669	 * and then do the main command loop for a normal edit.
670	 * If you quit out of a 'vi' command by doing Q or ^\,
671	 * you also fall through to here.
672	 */
673	seenprompt = 1;
674	ungetchar(0);
675	globp = 0;
676	initev = 0;
677	setlastchar('\n');
678	setexit();
679	commands(0, 0);
680	cleanup(1);
681	return (errcnt);
682}
683
684/*
685 * Initialization, before editing a new file.
686 * Main thing here is to get a new buffer (in fileinit),
687 * rest is peripheral state resetting.
688 */
689void
690init(void)
691{
692	int i;
693	void (*pstat)();
694	fileinit();
695	dot = zero = truedol = unddol = dol = fendcore;
696	one = zero+1;
697	undkind = UNDNONE;
698	chng = 0;
699	edited = 0;
700	for (i = 0; i <= 'z'-'a'+1; i++)
701		names[i] = 1;
702	anymarks = 0;
703	if (xflag) {
704		xtflag = 1;
705		/* ignore SIGINT before crypt process */
706		pstat = signal(SIGINT, SIG_IGN);
707		if (tpermflag)
708			(void) crypt_close(tperm);
709		tpermflag = 1;
710		if (makekey(tperm) != 0) {
711			xtflag = 0;
712			smerror(gettext(
713			    "Warning--Cannot encrypt temporary buffer\n"));
714		}
715		signal(SIGINT, pstat);
716	}
717}
718
719/*
720 * Return last component of unix path name p.
721 */
722unsigned char *
723tailpath(p)
724unsigned char *p;
725{
726	unsigned char *r;
727
728	for (r = p; *p; p++)
729		if (*p == '/')
730			r = p+1;
731	return (r);
732}
733
734
735/*
736 * validate_exrc - verify .exrc as belonging to the user.
737 * The file uid should match the process ruid,
738 * and the file should be writable only by the owner.
739 */
740static int
741validate_exrc(unsigned char *exrc_path)
742{
743	struct stat64 exrc_stat;
744	int process_uid;
745
746	if (stat64((char *)exrc_path, &exrc_stat) == -1)
747		return (0); /* ignore if .exrec is not found */
748	process_uid = geteuid();
749	/* if not root, uid must match file owner */
750	if (process_uid && process_uid != exrc_stat.st_uid)
751		return (-1);
752	if ((exrc_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0)
753		return (-1);
754	return (0);
755}
756
757/*
758 * print usage message to stdout
759 */
760static void
761usage(unsigned char *name)
762{
763	char buf[160];
764
765#ifdef TRACE
766	(void) snprintf(buf, sizeof (buf), gettext(
767	    "Usage: %s [- | -s] [-l] [-L] [-wn] "
768	    "[-R] [-S] [-r [file]] [-t tag] [-T] [-U tracefile]\n"
769	    "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name);
770#else
771	(void) snprintf(buf, sizeof (buf), gettext(
772	    "Usage: %s [- | -s] [-l] [-L] [-wn] "
773	    "[-R] [-S] [-r [file]] [-t tag]\n"
774	    "[-v] [-V] [-x] [-C] [+cmd | -c cmd] file...\n"), name);
775#endif
776	(void) write(2, buf, strlen(buf));
777}
778