Grammar change in the license declaration.
[citadel.git] / textclient / routines.c
1 // Client-side support functions.
2 //
3 // Copyright (c) 1987-2016 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, and/or
6 // disclosure is subject to the GNU General Purpose License version 3.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12
13 #include "textclient.h"
14
15 #define IFAIDE if(axlevel>=AxAideU)
16 #define IFNAIDE if (axlevel<AxAideU)
17
18 extern unsigned userflags;
19 extern char sigcaught;
20 extern char rc_floor_mode;
21 extern int rc_ansi_color;
22 extern int rc_prompt_control;
23
24 /* Destructive backspace */
25 void back(int spaces) {
26         int a;
27         for (a = 0; a < spaces; ++a) {
28                 scr_putc(8);
29                 scr_putc(32);
30                 scr_putc(8);
31         }
32 }
33
34 /*
35  * Edit a user's Internet email addresses
36  */
37 void edit_user_internet_email_addresses(CtdlIPC * ipc, char *who) {
38         char buf[SIZ];
39         char *resp = NULL;
40         int num_recs = 0;
41         char **recs = NULL;
42         char ch;
43         int i, j;
44         int quitting = 0;
45         int modified = 0;
46         int r;
47         char emailaddrs[512];
48
49         r = CtdlIPCAideGetEmailAddresses(ipc, who, emailaddrs, buf);
50         if (r / 100 == 1) {
51                 while (!IsEmptyStr(emailaddrs)) {
52                         extract_token(buf, emailaddrs, 0, '\n', sizeof buf);
53                         remove_token(emailaddrs, 0, '\n');
54                         if (!IsEmptyStr(buf)) {
55                                 ++num_recs;
56                                 if (num_recs == 1)
57                                         recs = malloc(sizeof(char *));
58                                 else
59                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
60                                 recs[num_recs - 1] = malloc(strlen(buf) + 1);
61                                 strcpy(recs[num_recs - 1], buf);
62                         }
63                 }
64         }
65
66         do {
67                 scr_printf("\n");
68                 color(BRIGHT_WHITE);
69                 scr_printf("    Internet email addresses for %s\n", who);
70                 color(DIM_WHITE);
71                 scr_printf("--- --------------------------------------------------\n");
72                 for (i = 0; i < num_recs; ++i) {
73                         color(DIM_WHITE);
74                         scr_printf("%3d ", i + 1);
75                         color(BRIGHT_CYAN);
76                         scr_printf("%s\n", recs[i]);
77                         color(DIM_WHITE);
78                 }
79
80                 ch = keymenu("", "<A>dd|<D>elete|<S>ave|<Q>uit");
81                 switch (ch) {
82                 case 'a':
83                         newprompt("Enter new email address: ", buf, 50);
84                         string_trim(buf);
85                         if (!IsEmptyStr(buf)) {
86                                 // FIXME validate the email address (format, our own domain, addr does not belong to another user)
87                                 ++num_recs;
88                                 if (num_recs == 1) {
89                                         recs = malloc(sizeof(char *));
90                                 }
91                                 else {
92                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
93                                 }
94                                 recs[num_recs - 1] = strdup(buf);
95                         }
96                         modified = 1;
97                         break;
98                 case 'd':
99                         i = intprompt("Delete which address", 1, 1, num_recs) - 1;
100                         free(recs[i]);
101                         --num_recs;
102                         for (j = i; j < num_recs; ++j) {
103                                 recs[j] = recs[j + 1];
104                         }
105                         modified = 1;
106                         break;
107                 case 's':
108                         r = 1;
109                         for (i = 0; i < num_recs; i++)
110                                 r += 1 + strlen(recs[i]);
111                         resp = (char *) calloc(1, r);
112                         if (!resp) {
113                                 scr_printf("Can't save config - out of memory!\n");
114                                 logoff(ipc, 1);
115                         }
116                         if (num_recs)
117                                 for (i = 0; i < num_recs; i++) {
118                                         strcat(resp, recs[i]);
119                                         strcat(resp, "\n");
120                                 }
121                         r = CtdlIPCAideSetEmailAddresses(ipc, who, resp, buf);
122                         if (r / 100 != 4) {
123                                 scr_printf("%s\n", buf);
124                         }
125                         else {
126                                 scr_printf("Saved %d addresses.\n", num_recs);
127                                 modified = 0;
128                                 quitting = 1;
129                         }
130                         free(resp);
131                         break;
132                 case 'q':
133                         quitting = !modified || boolprompt("Quit without saving", 0);
134                         break;
135                 default:
136                         break;
137                 }
138         } while (!quitting);
139
140         if (recs != NULL) {
141                 for (i = 0; i < num_recs; ++i)
142                         free(recs[i]);
143                 free(recs);
144         }
145 }
146
147
148 /*
149  * Edit or delete a user (cmd=25 to edit/create, 96 to delete)
150  */
151 void edituser(CtdlIPC * ipc, int cmd) {
152         char buf[SIZ];
153         char who[USERNAME_SIZE];
154         char newname[USERNAME_SIZE];
155         struct ctdluser *user = NULL;
156         int newnow = 0;
157         int r;                  /* IPC response code */
158         int change_name = 0;
159
160         strcpy(newname, "");
161
162         newprompt("User name: ", who, 29);
163         while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) {
164                 scr_printf("%s\n", buf);
165                 if (cmd == 25) {
166                         scr_printf("Do you want to create this user? ");
167                         if (yesno()) {
168                                 r = CtdlIPCCreateUser(ipc, who, 0, buf);
169                                 if (r / 100 == 2) {
170                                         newnow = 1;
171                                         continue;
172                                 }
173                                 scr_printf("%s\n", buf);
174                         }
175                 }
176                 free(user);
177                 return;
178         }
179
180         if (cmd == 25) {        // user edit
181
182                 /* val_user(ipc, user->fullname, 0); we used to display the vCard here but there's really no need */
183
184                 if (!newnow) {
185                         change_name = 1;
186                         while (change_name == 1) {
187                                 if (boolprompt("Change name", 0)) {
188                                         strprompt("New name", newname, USERNAME_SIZE - 1);
189                                         r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf);
190                                         if (r / 100 != 2) {
191                                                 scr_printf("%s\n", buf);
192                                         }
193                                         else {
194                                                 strcpy(user->fullname, newname);
195                                                 change_name = 0;
196                                         }
197                                 }
198                                 else {
199                                         change_name = 0;
200                                 }
201                         }
202                 }
203
204                 if (newnow || boolprompt("Change password", 0)) {
205                         strprompt("Password", user->password, -19);
206                 }
207
208                 user->axlevel = intprompt("Access level", user->axlevel, 0, 6);
209                 if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET))) {
210                         user->flags |= US_INTERNET;
211                 }
212                 else {
213                         user->flags &= ~US_INTERNET;
214                 }
215                 if (boolprompt("Ask user to register again", !(user->flags & US_REGIS))) {
216                         user->flags &= ~US_REGIS;
217                 }
218                 else {
219                         user->flags |= US_REGIS;
220                 }
221                 user->lastcall = boolprompt("Set last login to now", 0) ? time(NULL) : user->lastcall;
222                 user->USuserpurge = intprompt("Purge time (in days, 0 for system default", user->USuserpurge, 0, INT_MAX);
223         }
224
225         if (cmd == 96) {
226                 scr_printf("Do you want to delete this user? ");
227                 if (!yesno()) {
228                         free(user);
229                         return;
230                 }
231                 user->axlevel = AxDeleted;
232         }
233
234         r = CtdlIPCAideSetUserParameters(ipc, user, buf);
235         if (r / 100 != 2) {
236                 scr_printf("%s\n", buf);
237         }
238         free(user);
239
240         if (cmd == 25) {        // user edit
241                 if (boolprompt("Edit this user's Internet email addresses", 0)) {
242                         edit_user_internet_email_addresses(ipc, who);
243                 }
244         }
245
246 }
247
248
249 /* Display a prompt and flip a bit based on whether the user answers
250  * yes or no.  Yes=1 and No=0, unless 'backwards' is set to a nonzero value
251  * in which case No=1 and Yes=0.
252  */
253 int set_attr(CtdlIPC * ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards) {
254         int a;
255         int temp;
256
257         temp = sval;
258         color(DIM_WHITE);
259         scr_printf("%50s ", prompt);
260         color(DIM_MAGENTA);
261         scr_printf("[");
262         color(BRIGHT_MAGENTA);
263
264         if (backwards) {
265                 scr_printf("%3s", ((temp & sbit) ? "No" : "Yes"));
266         }
267         else {
268                 scr_printf("%3s", ((temp & sbit) ? "Yes" : "No"));
269         }
270
271         color(DIM_MAGENTA);
272         scr_printf("]? ");
273         color(BRIGHT_CYAN);
274         a = (temp & sbit);
275         if (a != 0)
276                 a = 1;
277         if (backwards)
278                 a = 1 - a;
279         a = yesno_d(a);
280         if (backwards)
281                 a = 1 - a;
282         color(DIM_WHITE);
283         temp = (temp | sbit);
284         if (!a)
285                 temp = (temp ^ sbit);
286         return (temp);
287 }
288
289 /*
290  * modes are:  0 - .EC command, 1 - .EC for new user,
291  *             2 - toggle Xpert mode  3 - toggle floor mode
292  */
293 void enter_config(CtdlIPC * ipc, int mode) {
294         char buf[SIZ];
295         struct ctdluser *user = NULL;
296         int r;                  /* IPC response code */
297
298         r = CtdlIPCGetConfig(ipc, &user, buf);
299         if (r / 100 != 2) {
300                 scr_printf("%s\n", buf);
301                 free(user);
302                 return;
303         }
304
305         if (mode == 0 || mode == 1) {
306
307                 user->flags = set_attr(ipc, user->flags, "Are you an experienced Citadel user", US_EXPERT, 0);
308                 if ((user->flags & US_EXPERT) == 0 && mode == 1) {
309                         free(user);
310                         return;
311                 }
312
313                 user->flags = set_attr(ipc, user->flags, "Print last old message on New message request", US_LASTOLD, 0);
314
315                 user->flags = set_attr(ipc, user->flags, "Prompt after each message", US_NOPROMPT, 1);
316
317                 if ((user->flags & US_NOPROMPT) == 0) {
318                         user->flags = set_attr(ipc, user->flags, "Use 'disappearing' prompts", US_DISAPPEAR, 0);
319                 }
320
321                 user->flags = set_attr(ipc, user->flags, "Pause after each screenful of text", US_PAGINATOR, 0);
322
323                 if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) {
324                         user->flags = set_attr(ipc, user->flags, "<N>ext and <S>top work at paginator prompt", US_PROMPTCTL, 0);
325                 }
326
327                 if (rc_floor_mode == RC_DEFAULT) {
328                         user->flags = set_attr(ipc, user->flags, "View rooms by floor", US_FLOORS, 0);
329                 }
330
331                 if (rc_ansi_color == 3) {
332                         user->flags = set_attr(ipc, user->flags, "Enable color support", US_COLOR, 0);
333                 }
334
335                 if ((user->flags & US_EXPERT) == 0) {
336                         formout(ipc, "unlisted");
337                 }
338
339                 user->flags = set_attr(ipc, user->flags, "Be unlisted in userlog", US_UNLISTED, 0);
340
341                 if (!IsEmptyStr(editor_path)) {
342                         user->flags = set_attr(ipc,
343                                                user->flags, "Always enter messages with the full-screen editor", US_EXTEDIT, 0);
344                 }
345
346         }
347
348         if (mode == 2) {
349                 if (user->flags & US_EXPERT) {
350                         user->flags ^= US_EXPERT;
351                         scr_printf("Expert mode now OFF\n");
352                 }
353                 else {
354                         user->flags |= US_EXPERT;
355                         scr_printf("Expert mode now ON\n");
356                 }
357         }
358
359         if (mode == 3) {
360                 if (user->flags & US_FLOORS) {
361                         user->flags ^= US_FLOORS;
362                         scr_printf("Floor mode now OFF\n");
363                 }
364                 else {
365                         user->flags |= US_FLOORS;
366                         scr_printf("Floor mode now ON\n");
367                 }
368         }
369
370         r = CtdlIPCSetConfig(ipc, user, buf);
371         if (r / 100 != 2)
372                 scr_printf("%s\n", buf);
373         userflags = user->flags;
374         free(user);
375 }
376
377 /*
378  * getstring()  -  get a line of text from a file
379  *                 ignores lines beginning with "#"
380  */
381 int getstring(FILE * fp, char *string) {
382         int a, c;
383         do {
384                 strcpy(string, "");
385                 a = 0;
386                 do {
387                         c = getc(fp);
388                         if (c < 0) {
389                                 string[a] = 0;
390                                 return (-1);
391                         }
392                         string[a++] = c;
393                 } while (c != 10);
394                 string[a - 1] = 0;
395         } while (string[0] == '#');
396         return (strlen(string));
397 }
398
399
400 /* Searches for patn in search string */
401 int pattern(char *search, char *patn) {
402         int a, b, len;
403
404         len = strlen(patn);
405         for (a = 0; !IsEmptyStr(&search[a]); ++a) {
406                 b = strncasecmp(&search[a], patn, len);
407                 if (b == 0)
408                         return (b);
409         }
410         return (-1);
411 }
412
413
414 void strproc(char *string) {
415         int a;
416
417         if (IsEmptyStr(string))
418                 return;
419
420         /* Convert non-printable characters to blanks */
421         for (a = 0; !IsEmptyStr(&string[a]); ++a) {
422                 if (string[a] < 32)
423                         string[a] = 32;
424                 if (string[a] > 126)
425                         string[a] = 32;
426         }
427
428         /* Remove leading and trailing blanks */
429         while (string[0] < 33)
430                 strcpy(string, &string[1]);
431         while (string[strlen(string) - 1] < 33)
432                 string[strlen(string) - 1] = 0;
433
434         /* Remove double blanks */
435         for (a = 0; a < strlen(string); ++a) {
436                 if ((string[a] == 32) && (string[a + 1] == 32)) {
437                         strcpy(&string[a], &string[a + 1]);
438                         a = 0;
439                 }
440         }
441
442         /* remove characters which would interfere with the network */
443         for (a = 0; a < strlen(string); ++a) {
444                 if (string[a] == '!')
445                         strcpy(&string[a], &string[a + 1]);
446                 if (string[a] == '@')
447                         strcpy(&string[a], &string[a + 1]);
448                 if (string[a] == '_')
449                         strcpy(&string[a], &string[a + 1]);
450                 if (string[a] == ',')
451                         strcpy(&string[a], &string[a + 1]);
452                 if (string[a] == '%')
453                         strcpy(&string[a], &string[a + 1]);
454                 if (string[a] == '|')
455                         strcpy(&string[a], &string[a + 1]);
456         }
457
458 }
459
460
461 void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax) {
462         static char dots[] = "**************************************************";
463         char dots_printed[51];
464         char fmt[42];
465         unsigned long a;
466
467         if (curr >= cmax) {
468                 scr_printf("\r%79s\r", "");
469         }
470         else {
471                 /* a will be range 0-50 rather than 0-100 */
472                 a = (curr * 50) / cmax;
473                 sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a);
474                 strncpy(dots_printed, dots, a);
475                 dots_printed[a] = 0;
476                 scr_printf(fmt, dots_printed, "", curr * 100 / cmax, curr, cmax);
477                 scr_flush();
478         }
479 }
480
481
482 /*
483  * NOT the same locate_host() in locate_host.c.  This one just does a
484  * 'who am i' to try to discover where the user is...
485  */
486 void locate_host(CtdlIPC * ipc, char *hbuf) {
487         FILE *who = (FILE *) popen("who am i", "r");
488         if (who == NULL) {
489                 strcpy(hbuf, ipc->ServInfo.fqdn);
490                 return;
491         }
492         fgets(hbuf, SIZ, who);
493         if (hbuf[strlen(hbuf) - 1] == '\n') {
494                 hbuf[strlen(hbuf) - 1] = 0;
495         }
496         pclose(who);
497         stripallbut(hbuf, '(', ')');
498 }
499
500 /*
501  * miscellaneous server commands (testing, etc.)
502  */
503 void misc_server_cmd(CtdlIPC * ipc, char *cmd) {
504         char buf[SIZ];
505
506         CtdlIPC_chat_send(ipc, cmd);
507         CtdlIPC_chat_recv(ipc, buf);
508         scr_printf("%s\n", buf);
509         if (buf[0] == '1') {
510                 set_keepalives(KA_HALF);
511                 while (CtdlIPC_chat_recv(ipc, buf), strcmp(buf, "000")) {
512                         scr_printf("%s\n", buf);
513                 }
514                 set_keepalives(KA_YES);
515                 return;
516         }
517         if (buf[0] == '4') {
518                 do {
519                         newprompt("> ", buf, 255);
520                         CtdlIPC_chat_send(ipc, buf);
521                 } while (strcmp(buf, "000"));
522                 return;
523         }
524 }
525
526
527 /*
528  * compute the checksum of a file
529  */
530 int file_checksum(char *filename) {
531         int cksum = 0;
532         int ch;
533         FILE *fp;
534
535         fp = fopen(filename, "r");
536         if (fp == NULL)
537                 return (0);
538
539         /* yes, this algorithm may allow cksum to overflow, but that's ok
540          * as long as it overflows consistently, which it will.
541          */
542         while (ch = getc(fp), ch >= 0) {
543                 cksum = (cksum + ch);
544         }
545
546         fclose(fp);
547         return (cksum);
548 }
549
550 /*
551  * nuke a directory and its contents
552  */
553 int nukedir(char *dirname) {
554         DIR *dp;
555         struct dirent *d;
556         char filename[SIZ];
557
558         dp = opendir(dirname);
559         if (dp == NULL) {
560                 return (errno);
561         }
562
563         while (d = readdir(dp), d != NULL) {
564                 snprintf(filename, sizeof filename, "%s/%s", dirname, d->d_name);
565                 unlink(filename);
566         }
567
568         closedir(dp);
569         return (rmdir(dirname));
570 }