1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2007 Nicholas Marriott <[email protected]> |
5 | * |
6 | * Permission to use, copy, modify, and distribute this software for any |
7 | * purpose with or without fee is hereby granted, provided that the above |
8 | * copyright notice and this permission notice appear in all copies. |
9 | * |
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | */ |
18 | |
19 | #include <sys/types.h> |
20 | #include <sys/stat.h> |
21 | #include <sys/utsname.h> |
22 | |
23 | #include <errno.h> |
24 | #include <event.h> |
25 | #include <fcntl.h> |
26 | #include <langinfo.h> |
27 | #include <locale.h> |
28 | #include <pwd.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | #include <time.h> |
32 | #include <unistd.h> |
33 | |
34 | #include "tmux.h" |
35 | |
36 | struct options *global_options; /* server options */ |
37 | struct options *global_s_options; /* session options */ |
38 | struct options *global_w_options; /* window options */ |
39 | struct environ *global_environ; |
40 | |
41 | struct timeval start_time; |
42 | const char *socket_path; |
43 | int ptm_fd = -1; |
44 | const char *shell_command; |
45 | |
46 | static __dead void usage(void); |
47 | static char *make_label(const char *, char **); |
48 | |
49 | static const char *getshell(void); |
50 | static int checkshell(const char *); |
51 | |
52 | static __dead void |
53 | usage(void) |
54 | { |
55 | fprintf(stderr, |
56 | "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" |
57 | " [-S socket-path] [command [flags]]\n" , |
58 | getprogname()); |
59 | exit(1); |
60 | } |
61 | |
62 | static const char * |
63 | getshell(void) |
64 | { |
65 | struct passwd *pw; |
66 | const char *shell; |
67 | |
68 | shell = getenv("SHELL" ); |
69 | if (checkshell(shell)) |
70 | return (shell); |
71 | |
72 | pw = getpwuid(getuid()); |
73 | if (pw != NULL && checkshell(pw->pw_shell)) |
74 | return (pw->pw_shell); |
75 | |
76 | return (_PATH_BSHELL); |
77 | } |
78 | |
79 | static int |
80 | checkshell(const char *shell) |
81 | { |
82 | if (shell == NULL || *shell != '/') |
83 | return (0); |
84 | if (areshell(shell)) |
85 | return (0); |
86 | if (access(shell, X_OK) != 0) |
87 | return (0); |
88 | return (1); |
89 | } |
90 | |
91 | int |
92 | areshell(const char *shell) |
93 | { |
94 | const char *progname, *ptr; |
95 | |
96 | if ((ptr = strrchr(shell, '/')) != NULL) |
97 | ptr++; |
98 | else |
99 | ptr = shell; |
100 | progname = getprogname(); |
101 | if (*progname == '-') |
102 | progname++; |
103 | if (strcmp(ptr, progname) == 0) |
104 | return (1); |
105 | return (0); |
106 | } |
107 | |
108 | static char * |
109 | make_label(const char *label, char **cause) |
110 | { |
111 | char *base, resolved[PATH_MAX], *path, *s; |
112 | struct stat sb; |
113 | uid_t uid; |
114 | |
115 | *cause = NULL; |
116 | |
117 | if (label == NULL) |
118 | label = "default" ; |
119 | uid = getuid(); |
120 | |
121 | if ((s = getenv("TMUX_TMPDIR" )) != NULL && *s != '\0') |
122 | xasprintf(&base, "%s/tmux-%ld" , s, (long)uid); |
123 | else |
124 | xasprintf(&base, "%s/tmux-%ld" , _PATH_TMP, (long)uid); |
125 | if (realpath(base, resolved) == NULL && |
126 | strlcpy(resolved, base, sizeof resolved) >= sizeof resolved) { |
127 | errno = ERANGE; |
128 | free(base); |
129 | goto fail; |
130 | } |
131 | free(base); |
132 | |
133 | if (mkdir(resolved, S_IRWXU) != 0 && errno != EEXIST) |
134 | goto fail; |
135 | if (lstat(resolved, &sb) != 0) |
136 | goto fail; |
137 | if (!S_ISDIR(sb.st_mode)) { |
138 | errno = ENOTDIR; |
139 | goto fail; |
140 | } |
141 | if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) { |
142 | errno = EACCES; |
143 | goto fail; |
144 | } |
145 | xasprintf(&path, "%s/%s" , resolved, label); |
146 | return (path); |
147 | |
148 | fail: |
149 | xasprintf(cause, "error creating %s (%s)" , resolved, strerror(errno)); |
150 | return (NULL); |
151 | } |
152 | |
153 | void |
154 | setblocking(int fd, int state) |
155 | { |
156 | int mode; |
157 | |
158 | if ((mode = fcntl(fd, F_GETFL)) != -1) { |
159 | if (!state) |
160 | mode |= O_NONBLOCK; |
161 | else |
162 | mode &= ~O_NONBLOCK; |
163 | fcntl(fd, F_SETFL, mode); |
164 | } |
165 | } |
166 | |
167 | const char * |
168 | find_cwd(void) |
169 | { |
170 | char resolved1[PATH_MAX], resolved2[PATH_MAX]; |
171 | static char cwd[PATH_MAX]; |
172 | const char *pwd; |
173 | |
174 | if (getcwd(cwd, sizeof cwd) == NULL) |
175 | return (NULL); |
176 | if ((pwd = getenv("PWD" )) == NULL || *pwd == '\0') |
177 | return (cwd); |
178 | |
179 | /* |
180 | * We want to use PWD so that symbolic links are maintained, |
181 | * but only if it matches the actual working directory. |
182 | */ |
183 | if (realpath(pwd, resolved1) == NULL) |
184 | return (cwd); |
185 | if (realpath(cwd, resolved2) == NULL) |
186 | return (cwd); |
187 | if (strcmp(resolved1, resolved2) != 0) |
188 | return (cwd); |
189 | return (pwd); |
190 | } |
191 | |
192 | const char * |
193 | find_home(void) |
194 | { |
195 | struct passwd *pw; |
196 | static const char *home; |
197 | |
198 | if (home != NULL) |
199 | return (home); |
200 | |
201 | home = getenv("HOME" ); |
202 | if (home == NULL || *home == '\0') { |
203 | pw = getpwuid(getuid()); |
204 | if (pw != NULL) |
205 | home = pw->pw_dir; |
206 | else |
207 | home = NULL; |
208 | } |
209 | |
210 | return (home); |
211 | } |
212 | |
213 | const char * |
214 | getversion(void) |
215 | { |
216 | return TMUX_VERSION; |
217 | } |
218 | |
219 | int |
220 | main(int argc, char **argv) |
221 | { |
222 | char *path, *label, *cause, **var; |
223 | const char *s, *shell, *cwd; |
224 | int opt, flags, keys; |
225 | const struct options_table_entry *oe; |
226 | |
227 | if (setlocale(LC_CTYPE, "en_US.UTF-8" ) == NULL && |
228 | setlocale(LC_CTYPE, "C.UTF-8" ) == NULL) { |
229 | if (setlocale(LC_CTYPE, "" ) == NULL) |
230 | errx(1, "invalid LC_ALL, LC_CTYPE or LANG" ); |
231 | s = nl_langinfo(CODESET); |
232 | if (strcasecmp(s, "UTF-8" ) != 0 && strcasecmp(s, "UTF8" ) != 0) |
233 | errx(1, "need UTF-8 locale (LC_CTYPE) but have %s" , s); |
234 | } |
235 | |
236 | setlocale(LC_TIME, "" ); |
237 | tzset(); |
238 | |
239 | if (**argv == '-') |
240 | flags = CLIENT_LOGIN; |
241 | else |
242 | flags = 0; |
243 | |
244 | label = path = NULL; |
245 | while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV" )) != -1) { |
246 | switch (opt) { |
247 | case '2': |
248 | flags |= CLIENT_256COLOURS; |
249 | break; |
250 | case 'c': |
251 | shell_command = optarg; |
252 | break; |
253 | case 'C': |
254 | if (flags & CLIENT_CONTROL) |
255 | flags |= CLIENT_CONTROLCONTROL; |
256 | else |
257 | flags |= CLIENT_CONTROL; |
258 | break; |
259 | case 'f': |
260 | set_cfg_file(optarg); |
261 | break; |
262 | case 'V': |
263 | printf("%s %s\n" , getprogname(), getversion()); |
264 | exit(0); |
265 | case 'l': |
266 | flags |= CLIENT_LOGIN; |
267 | break; |
268 | case 'L': |
269 | free(label); |
270 | label = xstrdup(optarg); |
271 | break; |
272 | case 'q': |
273 | break; |
274 | case 'S': |
275 | free(path); |
276 | path = xstrdup(optarg); |
277 | break; |
278 | case 'u': |
279 | flags |= CLIENT_UTF8; |
280 | break; |
281 | case 'v': |
282 | log_add_level(); |
283 | break; |
284 | default: |
285 | usage(); |
286 | } |
287 | } |
288 | argc -= optind; |
289 | argv += optind; |
290 | |
291 | if (shell_command != NULL && argc != 0) |
292 | usage(); |
293 | |
294 | if ((ptm_fd = getptmfd()) == -1) |
295 | err(1, "getptmfd" ); |
296 | if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd " |
297 | "recvfd proc exec tty ps" , NULL) != 0) |
298 | err(1, "pledge" ); |
299 | |
300 | /* |
301 | * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8. |
302 | * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain |
303 | * UTF-8, it is a safe assumption that either they are using a UTF-8 |
304 | * terminal, or if not they know that output from UTF-8-capable |
305 | * programs may be wrong. |
306 | */ |
307 | if (getenv("TMUX" ) != NULL) |
308 | flags |= CLIENT_UTF8; |
309 | else { |
310 | s = getenv("LC_ALL" ); |
311 | if (s == NULL || *s == '\0') |
312 | s = getenv("LC_CTYPE" ); |
313 | if (s == NULL || *s == '\0') |
314 | s = getenv("LANG" ); |
315 | if (s == NULL || *s == '\0') |
316 | s = "" ; |
317 | if (strcasestr(s, "UTF-8" ) != NULL || |
318 | strcasestr(s, "UTF8" ) != NULL) |
319 | flags |= CLIENT_UTF8; |
320 | } |
321 | |
322 | global_environ = environ_create(); |
323 | for (var = environ; *var != NULL; var++) |
324 | environ_put(global_environ, *var); |
325 | if ((cwd = find_cwd()) != NULL) |
326 | environ_set(global_environ, "PWD" , "%s" , cwd); |
327 | |
328 | global_options = options_create(NULL); |
329 | global_s_options = options_create(NULL); |
330 | global_w_options = options_create(NULL); |
331 | for (oe = options_table; oe->name != NULL; oe++) { |
332 | if (oe->scope & OPTIONS_TABLE_SERVER) |
333 | options_default(global_options, oe); |
334 | if (oe->scope & OPTIONS_TABLE_SESSION) |
335 | options_default(global_s_options, oe); |
336 | if (oe->scope & OPTIONS_TABLE_WINDOW) |
337 | options_default(global_w_options, oe); |
338 | } |
339 | |
340 | /* |
341 | * The default shell comes from SHELL or from the user's passwd entry |
342 | * if available. |
343 | */ |
344 | shell = getshell(); |
345 | options_set_string(global_s_options, "default-shell" , 0, "%s" , shell); |
346 | |
347 | /* Override keys to vi if VISUAL or EDITOR are set. */ |
348 | if ((s = getenv("VISUAL" )) != NULL || (s = getenv("EDITOR" )) != NULL) { |
349 | if (strrchr(s, '/') != NULL) |
350 | s = strrchr(s, '/') + 1; |
351 | if (strstr(s, "vi" ) != NULL) |
352 | keys = MODEKEY_VI; |
353 | else |
354 | keys = MODEKEY_EMACS; |
355 | options_set_number(global_s_options, "status-keys" , keys); |
356 | options_set_number(global_w_options, "mode-keys" , keys); |
357 | } |
358 | |
359 | /* |
360 | * If socket is specified on the command-line with -S or -L, it is |
361 | * used. Otherwise, $TMUX is checked and if that fails "default" is |
362 | * used. |
363 | */ |
364 | if (path == NULL && label == NULL) { |
365 | s = getenv("TMUX" ); |
366 | if (s != NULL && *s != '\0' && *s != ',') { |
367 | path = xstrdup(s); |
368 | path[strcspn(path, "," )] = '\0'; |
369 | } |
370 | } |
371 | if (path == NULL && (path = make_label(label, &cause)) == NULL) { |
372 | if (cause != NULL) { |
373 | fprintf(stderr, "%s\n" , cause); |
374 | free(cause); |
375 | } |
376 | exit(1); |
377 | } |
378 | socket_path = path; |
379 | free(label); |
380 | |
381 | /* Pass control to the client. */ |
382 | exit(client_main(osdep_event_init(), argc, argv, flags)); |
383 | } |
384 | |