3daaa3da86f06f1ca03565c25103278fec5e2238
[citadel.git] / textclient / routines.c
1 /*
2  * Client-side support functions.
3  *
4  * Copyright (c) 1987-2016 by the citadel.org team
5  *
6  *  This program is open source software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 3.
8  *
9  *  This program 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
15 #include "textclient.h"
16
17 #define IFAIDE if(axlevel>=AxAideU)
18 #define IFNAIDE if (axlevel<AxAideU)
19
20 extern unsigned userflags;
21 extern char sigcaught;
22 extern char rc_floor_mode;
23 extern int rc_ansi_color;
24 extern int rc_prompt_control;
25
26 /* Destructive backspace */
27 void back(int spaces)
28 {
29         int a;
30         for (a = 0; a < spaces; ++a) {
31                 scr_putc(8);
32                 scr_putc(32);
33                 scr_putc(8);
34         }
35 }
36
37 /*
38  * Edit a user's Internet email addresses
39  */
40 void edit_user_internet_email_addresses(CtdlIPC * ipc, char *who)
41 {
42         char buf[SIZ];
43         char *resp = NULL;
44         int num_recs = 0;
45         char **recs = NULL;
46         char ch;
47         int i, j;
48         int quitting = 0;
49         int modified = 0;
50         int r;
51         char emailaddrs[512];
52
53         r = CtdlIPCAideGetEmailAddresses(ipc, who, emailaddrs, buf);
54         if (r / 100 == 1) {
55                 while (!IsEmptyStr(emailaddrs)) {
56                         extract_token(buf, emailaddrs, 0, '\n', sizeof buf);
57                         remove_token(emailaddrs, 0, '\n');
58                         if (!IsEmptyStr(buf)) {
59                                 ++num_recs;
60                                 if (num_recs == 1)
61                                         recs = malloc(sizeof(char *));
62                                 else
63                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
64                                 recs[num_recs - 1] = malloc(strlen(buf) + 1);
65                                 strcpy(recs[num_recs - 1], buf);
66                         }
67                 }
68         }
69
70         do {
71                 scr_printf("\n");
72                 color(BRIGHT_WHITE);
73                 scr_printf("    Internet email addresses for %s\n", who);
74                 color(DIM_WHITE);
75                 scr_printf("--- --------------------------------------------------\n");
76                 for (i = 0; i < num_recs; ++i) {
77                         color(DIM_WHITE);
78                         scr_printf("%3d ", i + 1);
79                         color(BRIGHT_CYAN);
80                         scr_printf("%s\n", recs[i]);
81                         color(DIM_WHITE);
82                 }
83
84                 ch = keymenu("", "<A>dd|<D>elete|<S>ave|<Q>uit");
85                 switch (ch) {
86                 case 'a':
87                         newprompt("Enter new email address: ", buf, 50);
88                         striplt(buf);
89                         if (!IsEmptyStr(buf)) {
90                                 // FIXME validate the email address (format, our own domain, addr does not belong to another user)
91                                 ++num_recs;
92                                 if (num_recs == 1) {
93                                         recs = malloc(sizeof(char *));
94                                 } else {
95                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
96                                 }
97                                 recs[num_recs - 1] = strdup(buf);
98                         }
99                         modified = 1;
100                         break;
101                 case 'd':
102                         i = intprompt("Delete which address", 1, 1, num_recs) - 1;
103                         free(recs[i]);
104                         --num_recs;
105                         for (j = i; j < num_recs; ++j) {
106                                 recs[j] = recs[j + 1];
107                         }
108                         modified = 1;
109                         break;
110                 case 's':
111                         r = 1;
112                         for (i = 0; i < num_recs; i++)
113                                 r += 1 + strlen(recs[i]);
114                         resp = (char *) calloc(1, r);
115                         if (!resp) {
116                                 scr_printf("Can't save config - out of memory!\n");
117                                 logoff(ipc, 1);
118                         }
119                         if (num_recs)
120                                 for (i = 0; i < num_recs; i++) {
121                                         strcat(resp, recs[i]);
122                                         strcat(resp, "\n");
123                                 }
124                         r = CtdlIPCAideSetEmailAddresses(ipc, who, resp, buf);
125                         if (r / 100 != 4) {
126                                 scr_printf("%s\n", buf);
127                         } else {
128                                 scr_printf("Saved %d addresses.\n", num_recs);
129                                 modified = 0;
130                                 quitting = 1;
131                         }
132                         free(resp);
133                         break;
134                 case 'q':
135                         quitting = !modified || boolprompt("Quit without saving", 0);
136                         break;
137                 default:
138                         break;
139                 }
140         } while (!quitting);
141
142         if (recs != NULL) {
143                 for (i = 0; i < num_recs; ++i)
144                         free(recs[i]);
145                 free(recs);
146         }
147 }
148
149
150 /*
151  * Edit or delete a user (cmd=25 to edit/create, 96 to delete)
152  */
153 void edituser(CtdlIPC * ipc, int cmd)
154 {
155         char buf[SIZ];
156         char who[USERNAME_SIZE];
157         char newname[USERNAME_SIZE];
158         struct ctdluser *user = NULL;
159         int newnow = 0;
160         int r;                  /* IPC response code */
161         int change_name = 0;
162
163         strcpy(newname, "");
164
165         newprompt("User name: ", who, 29);
166         while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) {
167                 scr_printf("%s\n", buf);
168                 if (cmd == 25) {
169                         scr_printf("Do you want to create this user? ");
170                         if (yesno()) {
171                                 r = CtdlIPCCreateUser(ipc, who, 0, buf);
172                                 if (r / 100 == 2) {
173                                         newnow = 1;
174                                         continue;
175                                 }
176                                 scr_printf("%s\n", buf);
177                         }
178                 }
179                 free(user);
180                 return;
181         }
182
183         if (cmd == 25) {        // user edit
184
185                 /* val_user(ipc, user->fullname, 0); we used to display the vCard here but there's really no need */
186
187                 if (!newnow) {
188                         change_name = 1;
189                         while (change_name == 1) {
190                                 if (boolprompt("Change name", 0)) {
191                                         strprompt("New name", newname, USERNAME_SIZE - 1);
192                                         r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf);
193                                         if (r / 100 != 2) {
194                                                 scr_printf("%s\n", buf);
195                                         } else {
196                                                 strcpy(user->fullname, newname);
197                                                 change_name = 0;
198                                         }
199                                 } else {
200                                         change_name = 0;
201                                 }
202                         }
203                 }
204
205                 if (newnow || boolprompt("Change password", 0)) {
206                         strprompt("Password", user->password, -19);
207                 }
208
209                 user->axlevel = intprompt("Access level", user->axlevel, 0, 6);
210                 if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET))) {
211                         user->flags |= US_INTERNET;
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                 } else {
218                         user->flags |= US_REGIS;
219                 }
220                 user->timescalled = intprompt("Times called", user->timescalled, 0, INT_MAX);
221                 user->posted = intprompt("Messages posted", user->posted, 0, INT_MAX);
222                 user->lastcall = boolprompt("Set last login to now", 0) ? time(NULL) : user->lastcall;
223                 user->USuserpurge = intprompt("Purge time (in days, 0 for system default", user->USuserpurge, 0, INT_MAX);
224         }
225
226         if (cmd == 96) {
227                 scr_printf("Do you want to delete this user? ");
228                 if (!yesno()) {
229                         free(user);
230                         return;
231                 }
232                 user->axlevel = AxDeleted;
233         }
234
235         r = CtdlIPCAideSetUserParameters(ipc, user, buf);
236         if (r / 100 != 2) {
237                 scr_printf("%s\n", buf);
238         }
239         free(user);
240
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 /* Display a prompt and flip a bit based on whether the user answers
249  * yes or no.  Yes=1 and No=0, unless 'backwards' is set to a nonzero value
250  * in which case No=1 and Yes=0.
251  */
252 int set_attr(CtdlIPC * ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards)
253 {
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         } else {
267                 scr_printf("%3s", ((temp & sbit) ? "Yes" : "No"));
268         }
269
270         color(DIM_MAGENTA);
271         scr_printf("]? ");
272         color(BRIGHT_CYAN);
273         a = (temp & sbit);
274         if (a != 0)
275                 a = 1;
276         if (backwards)
277                 a = 1 - a;
278         a = yesno_d(a);
279         if (backwards)
280                 a = 1 - a;
281         color(DIM_WHITE);
282         temp = (temp | sbit);
283         if (!a)
284                 temp = (temp ^ sbit);
285         return (temp);
286 }
287
288 /*
289  * modes are:  0 - .EC command, 1 - .EC for new user,
290  *             2 - toggle Xpert mode  3 - toggle floor mode
291  */
292 void enter_config(CtdlIPC * ipc, int mode)
293 {
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                 } else {
353                         user->flags |= US_EXPERT;
354                         scr_printf("Expert mode now ON\n");
355                 }
356         }
357
358         if (mode == 3) {
359                 if (user->flags & US_FLOORS) {
360                         user->flags ^= US_FLOORS;
361                         scr_printf("Floor mode now OFF\n");
362                 } else {
363                         user->flags |= US_FLOORS;
364                         scr_printf("Floor mode now ON\n");
365                 }
366         }
367
368         r = CtdlIPCSetConfig(ipc, user, buf);
369         if (r / 100 != 2)
370                 scr_printf("%s\n", buf);
371         userflags = user->flags;
372         free(user);
373 }
374
375 /*
376  * getstring()  -  get a line of text from a file
377  *                 ignores lines beginning with "#"
378  */
379 int getstring(FILE * fp, char *string)
380 {
381         int a, c;
382         do {
383                 strcpy(string, "");
384                 a = 0;
385                 do {
386                         c = getc(fp);
387                         if (c < 0) {
388                                 string[a] = 0;
389                                 return (-1);
390                         }
391                         string[a++] = c;
392                 } while (c != 10);
393                 string[a - 1] = 0;
394         } while (string[0] == '#');
395         return (strlen(string));
396 }
397
398
399 /* Searches for patn in search string */
400 int pattern(char *search, char *patn)
401 {
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 {
416         int a;
417
418         if (IsEmptyStr(string))
419                 return;
420
421         /* Convert non-printable characters to blanks */
422         for (a = 0; !IsEmptyStr(&string[a]); ++a) {
423                 if (string[a] < 32)
424                         string[a] = 32;
425                 if (string[a] > 126)
426                         string[a] = 32;
427         }
428
429         /* Remove leading and trailing blanks */
430         while (string[0] < 33)
431                 strcpy(string, &string[1]);
432         while (string[strlen(string) - 1] < 33)
433                 string[strlen(string) - 1] = 0;
434
435         /* Remove double blanks */
436         for (a = 0; a < strlen(string); ++a) {
437                 if ((string[a] == 32) && (string[a + 1] == 32)) {
438                         strcpy(&string[a], &string[a + 1]);
439                         a = 0;
440                 }
441         }
442
443         /* remove characters which would interfere with the network */
444         for (a = 0; a < strlen(string); ++a) {
445                 if (string[a] == '!')
446                         strcpy(&string[a], &string[a + 1]);
447                 if (string[a] == '@')
448                         strcpy(&string[a], &string[a + 1]);
449                 if (string[a] == '_')
450                         strcpy(&string[a], &string[a + 1]);
451                 if (string[a] == ',')
452                         strcpy(&string[a], &string[a + 1]);
453                 if (string[a] == '%')
454                         strcpy(&string[a], &string[a + 1]);
455                 if (string[a] == '|')
456                         strcpy(&string[a], &string[a + 1]);
457         }
458
459 }
460
461
462 void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax)
463 {
464         static char dots[] = "**************************************************";
465         char dots_printed[51];
466         char fmt[42];
467         unsigned long a;
468
469         if (curr >= cmax) {
470                 scr_printf("\r%79s\r", "");
471         } else {
472                 /* a will be range 0-50 rather than 0-100 */
473                 a = (curr * 50) / cmax;
474                 sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a);
475                 strncpy(dots_printed, dots, a);
476                 dots_printed[a] = 0;
477                 scr_printf(fmt, dots_printed, "", curr * 100 / cmax, curr, cmax);
478                 scr_flush();
479         }
480 }
481
482
483 /*
484  * NOT the same locate_host() in locate_host.c.  This one just does a
485  * 'who am i' to try to discover where the user is...
486  */
487 void locate_host(CtdlIPC * ipc, char *hbuf)
488 {
489         FILE *who = (FILE *) popen("who am i", "r");
490         if (who == NULL) {
491                 strcpy(hbuf, ipc->ServInfo.fqdn);
492                 return;
493         }
494         fgets(hbuf, SIZ, who);
495         pclose(who);
496         stripallbut(hbuf, '(', ')');
497 }
498
499 /*
500  * miscellaneous server commands (testing, etc.)
501  */
502 void misc_server_cmd(CtdlIPC * ipc, char *cmd)
503 {
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 {
532         int cksum = 0;
533         int ch;
534         FILE *fp;
535
536         fp = fopen(filename, "r");
537         if (fp == NULL)
538                 return (0);
539
540         /* yes, this algorithm may allow cksum to overflow, but that's ok
541          * as long as it overflows consistently, which it will.
542          */
543         while (ch = getc(fp), ch >= 0) {
544                 cksum = (cksum + ch);
545         }
546
547         fclose(fp);
548         return (cksum);
549 }
550
551 /*
552  * nuke a directory and its contents
553  */
554 int nukedir(char *dirname)
555 {
556         DIR *dp;
557         struct dirent *d;
558         char filename[SIZ];
559
560         dp = opendir(dirname);
561         if (dp == NULL) {
562                 return (errno);
563         }
564
565         while (d = readdir(dp), d != NULL) {
566                 snprintf(filename, sizeof filename, "%s/%s", dirname, d->d_name);
567                 unlink(filename);
568         }
569
570         closedir(dp);
571         return (rmdir(dirname));
572 }