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