1 /**
2 * Copyright (C) 2019 Aaron Ball <nullspoon@oper.io>
3 *
4 * Terminus is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Terminus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with terminus. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <time.h>
21
22 #define SECONDS_IN_DAY 86400
23
24
25 /**
26 * Prints the helptext
27 */
28 void usage() {
29 printf(
30 "Terminus is a program to trigger another program or script if the\n"
31 "configured passive activity threshold is exceeded. For example, if a user\n"
32 "does not log in to their system more recently than the configured duration,\n"
33 "then the configured program or script is executed by terminus on the next\n"
34 "login.\n"
35 "\n"
36 "Terminus is intended for use within the shell's profile/rc file so it is\n"
37 "called every time a new shell is loaded, which will update the lastlogin\n"
38 "timestamp as the user uses the system.\n"
39 "\n"
40 "Usage:\n"
41 " terminus <days> <command to execute>\n"
42 "\n"
43 "Options:\n"
44 " -h,--help Print this help text\n"
45 " -r,--reset Reset last login timestamp to current time\n"
46 );
47 }
48
49
50 /**
51 * Writes the current timestamp to the specified lastlogin file
52 *
53 * @param path Path to the lastlogin file
54 *
55 * @return int Status of the write operation
56 */
57 int write_lastlogin(char* path) {
58 FILE* f = fopen(path, "w+");
59
60 // Exit if file cannot be opened for writing
61 if(f == NULL) { return 1; }
62
63 // Write the current time
64 fprintf(f, "%d", (int) time(NULL));
65
66 fclose(f);
67 return 0;
68 }
69
70
71 /**
72 * Reads a long from a file. Only reads the first line of the file and only
73 * numbers are accounted for (strtol handles the parsing).
74 *
75 * @path Path to the file to read
76 *
77 * @return Long read from the specified file
78 */
79 long fread_long(char* path) {
80 FILE* fd;
81 char buf[32];
82 fd = fopen(path, "r");
83 if(!fd)
84 return -1;
85 fgets(buf, 32, fd);
86 fclose(fd);
87 return strtol(buf, NULL, 10);
88 }
89
90
91 /**
92 * Executes the specified command, printing the output.
93 * Note that this does read the calling shell's environmental variables, and so
94 * absolute paths are not necessary if the command targets an executable in
95 * PATH.
96 *
97 * @param cmd Command to be executed and output to be printed
98 *
99 * @return int Execution status of the command
100 * Note that if the command fails execution at start, -1 is
101 * returned.
102 */
103 int exec_cmd(char* cmd) {
104 FILE* p = popen(cmd, "r");
105 if(p == NULL) { return -1; }
106
107 // Buffer for each line
108 char line[256];
109 // For each line, print the output
110 while (fgets(line, sizeof(line)-1, p) != NULL) {
111 printf("%s", line);
112 }
113
114 return pclose(p);
115 }
116
117
118 /**
119 * Ye olde main function
120 */
121 int main(int argc, char* argv[]) {
122 int days;
123 char* cmd;
124 // Expected path to lastlogin file
125 char lastlogin_path[256];
126 // Time-related variables
127 time_t now;
128 time_t threshold;
129 time_t lastlogin;
130
131 if(argc == 1) {
132 usage();
133 return 1;
134 }
135
136 // Compose path to lastlogin file
137 if(!getenv("HOME")) {
138 printf("ERROR: HOME is unset\n");
139 return 1;
140 }
141
142 sprintf(lastlogin_path, "%s/.lastlogin", getenv("HOME"));
143
144 // Parse program arguments
145 for(int i=1; i<argc; i++) {
146 if(strcmp("-r", argv[i]) == 0 || strcmp("--reset", argv[i]) == 0) {
147 // Reset flag set. Override default behavior and write current timestamp
148 // to lastlogin file
149 if(write_lastlogin(lastlogin_path) == 1) {
150 printf("Lastlogin reset failure.\n"
151 "Unable to write to file %s.\n", lastlogin_path);
152 return 1;
153 }
154 // Exit early since we're force resetting lastlogin.
155 return 0;
156 } else if(strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
157 // The user requested the help text
158 usage();
159 return 0;
160 }
161 }
162
163 // Get program arguments
164 days = atoi(argv[1]);
165 cmd = argv[2];
166
167 // Set time-related values
168 now = time(NULL);
169 threshold = days * SECONDS_IN_DAY;
170 lastlogin = fread_long(lastlogin_path);
171
172 // Set lastlogin to "now" if no lastlogin file is found
173 if(lastlogin == -1) {
174 fprintf(stderr, "Lastlogin file could not be read. Initializing.\n");
175 lastlogin = time(NULL);
176 }
177
178 // Determine if I'm dead
179 if((now - lastlogin) > threshold) {
180 printf("You're dead!\n");
181 return exec_cmd(cmd);
182 } else if((lastlogin - now) > SECONDS_IN_DAY) {
183 // Abort if last login is in the future.
184 fprintf(stderr,
185 "ERROR: Last login appears to be in the future. This likely means\n"
186 "something is wrong with the system clock.\n"
187 );
188 return 1;
189 }
190
191 // All other checks passed, update the lastlogin file
192 if(write_lastlogin(lastlogin_path) == 1) {
193 fprintf(stderr, "Failure writing lastlogin\n");
194 return 1;
195 }
196 printf("You're not dead!\n");
197 return 0;
198 }
|