Updated the boilerplate on each file
[citadel] / textclient / citadel_ipc.c
1 // Copyright (c) 1987-2020 by the citadel.org team
2 //
3 // This program is open source software.  Use, duplication, and/or
4 // disclosure are subject to the GNU General Purpose License version 3.
5 //
6 // This program is distributed in the hope that it will be useful,
7 // but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 // GNU General Public License for more details.
10
11 #include "textclient.h"
12
13 #ifdef HAVE_OPENSSL
14 static SSL_CTX *ssl_ctx;
15 char arg_encrypt;
16 char rc_encrypt;
17
18 #endif                          /* HAVE_OPENSSL */
19
20 #ifndef INADDR_NONE
21 #define INADDR_NONE 0xffffffff
22 #endif
23
24 static void (*status_hook) (char *s) = NULL;
25 char ctdl_autoetc_dir[PATH_MAX] = "";
26 char file_citadel_rc[PATH_MAX] = "";
27 char ctdl_run_dir[PATH_MAX] = "";
28 char ctdl_etc_dir[PATH_MAX] = "";
29 char ctdl_home_directory[PATH_MAX] = "";
30 char file_citadel_socket[PATH_MAX] = "";
31
32 char *viewdefs[] = {
33         "Messages",
34         "Summary",
35         "Address book",
36         "Calendar",
37         "Tasks"
38 };
39
40 char *axdefs[] = {
41         "Deleted",
42         "New User",
43         "Problem User",
44         "Local User",
45         "Normal User",
46         "Preferred User",
47         "Admin",
48         "Admin"
49 };
50
51
52 void CtdlIPC_lock(CtdlIPC * ipc)
53 {
54         if (ipc->network_status_cb)
55                 ipc->network_status_cb(1);
56 }
57
58
59 void CtdlIPC_unlock(CtdlIPC * ipc)
60 {
61         if (ipc->network_status_cb)
62                 ipc->network_status_cb(0);
63 }
64
65
66 char *libcitadelclient_version_string(void)
67 {
68         return "libcitadelclient(unnumbered)";
69 }
70
71
72
73
74 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
75         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
76                          (home&!relh)?ctdl_home_directory:basedir, \
77              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
78              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
79                          relhome, \
80              (relhome[0]!='\0')?"/":"",\
81                          dirbuffer,\
82                          (dirbuffer[0]!='\0')?"/":"");
83
84 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
85
86
87 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
88 {
89         const char *basedir = "";
90         char dirbuffer[PATH_MAX] = "";
91
92         StripSlashes(ctdldir, 1);
93
94 #ifndef HAVE_RUN_DIR
95         basedir = ctdldir;
96 #else
97         basedir = RUN_DIR;
98 #endif
99         COMPUTE_DIRECTORY(ctdl_run_dir);
100         StripSlashes(ctdl_run_dir, 1);
101
102
103 #ifndef HAVE_AUTO_ETC_DIR
104         basedir = ctdldir;
105 #else
106         basedir = AUTO_ETC_DIR;
107 #endif
108         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
109         StripSlashes(ctdl_autoetc_dir, 1);
110
111
112 #ifndef HAVE_ETC_DIR
113         basedir = ctdldir;
114 #else
115         basedir = ETC_DIR;
116 #endif
117         COMPUTE_DIRECTORY(ctdl_etc_dir);
118         StripSlashes(ctdl_etc_dir, 1);
119
120
121
122         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
123         StripSlashes(file_citadel_rc, 0);
124
125         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
126         StripSlashes(file_citadel_socket, 0);
127
128         DBG_PRINT(ctdl_run_dir);
129         DBG_PRINT(file_citadel_socket);
130         DBG_PRINT(ctdl_etc_dir);
131         DBG_PRINT(file_citadel_rc);
132 }
133
134 void setCryptoStatusHook(void (*hook) (char *s))
135 {
136         status_hook = hook;
137 }
138
139 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook) (int state))
140 {
141         ipc->network_status_cb = hook;
142 }
143
144
145 char instant_msgs = 0;
146
147
148 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
149 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
150 #ifdef HAVE_OPENSSL
151 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
152 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
153 static void endtls(SSL * ssl);
154 #endif                          /* HAVE_OPENSSL */
155 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
156 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
157
158
159
160 const char *svn_revision(void);
161
162 /*
163  * Does nothing.  The server should always return 200.
164  */
165 int CtdlIPCNoop(CtdlIPC * ipc)
166 {
167         char aaa[128];
168
169         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
170 }
171
172
173 /*
174  * Does nothing interesting.  The server should always return 200
175  * along with your string.
176  */
177 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret)
178 {
179         int ret;
180         char *aaa;
181
182         if (!arg)
183                 return -2;
184         if (!cret)
185                 return -2;
186
187         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
188         if (!aaa)
189                 return -1;
190
191         sprintf(aaa, "ECHO %s", arg);
192         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
193         free(aaa);
194         return ret;
195 }
196
197
198 /*
199  * Asks the server to close the connecction.
200  * Should always return 200.
201  */
202 int CtdlIPCQuit(CtdlIPC * ipc)
203 {
204         int ret = 221;          /* Default to successful quit */
205         char aaa[SIZ];
206
207         CtdlIPC_lock(ipc);
208         if (ipc->sock > -1) {
209                 CtdlIPC_putline(ipc, "QUIT");
210                 CtdlIPC_getline(ipc, aaa);
211                 ret = atoi(aaa);
212         }
213 #ifdef HAVE_OPENSSL
214         if (ipc->ssl)
215                 SSL_shutdown(ipc->ssl);
216         ipc->ssl = NULL;
217 #endif
218         if (ipc->sock)
219                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
220         ipc->sock = -1;
221         CtdlIPC_unlock(ipc);
222         return ret;
223 }
224
225
226 /*
227  * Asks the server to log out.  Should always return 200, even if no user
228  * was logged in.  The user will not be logged in after this!
229  */
230 int CtdlIPCLogout(CtdlIPC * ipc)
231 {
232         int ret;
233         char aaa[SIZ];
234
235         CtdlIPC_lock(ipc);
236         CtdlIPC_putline(ipc, "LOUT");
237         CtdlIPC_getline(ipc, aaa);
238         ret = atoi(aaa);
239         CtdlIPC_unlock(ipc);
240         return ret;
241 }
242
243
244 /*
245  * First stage of authentication - pass the username.  Returns 300 if the
246  * username is able to log in, with the username correctly spelled in cret.
247  * Returns various 500 error codes if the user doesn't exist, etc.
248  */
249 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret)
250 {
251         int ret;
252         char *aaa;
253
254         if (!username)
255                 return -2;
256         if (!cret)
257                 return -2;
258
259         aaa = (char *) malloc((size_t) (strlen(username) + 6));
260         if (!aaa)
261                 return -1;
262
263         sprintf(aaa, "USER %s", username);
264         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
265         free(aaa);
266         return ret;
267 }
268
269
270 /*
271  * Second stage of authentication - provide password.  The server returns
272  * 200 and several arguments in cret relating to the user's account.
273  */
274 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret)
275 {
276         int ret;
277         char *aaa;
278
279         if (!passwd)
280                 return -2;
281         if (!cret)
282                 return -2;
283
284         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
285         if (!aaa)
286                 return -1;
287
288         sprintf(aaa, "PASS %s", passwd);
289         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
290         free(aaa);
291         return ret;
292 }
293
294
295 /*
296  * Second stage of authentication - provide password.  The server returns
297  * 200 and several arguments in cret relating to the user's account.
298  */
299 int CtdlIPCTryApopPassword(CtdlIPC * ipc, const char *response, char *cret)
300 {
301         int ret;
302         char *aaa;
303
304         if (!response)
305                 return -2;
306         if (!cret)
307                 return -2;
308
309         aaa = (char *) malloc((size_t) (strlen(response) + 6));
310         if (!aaa)
311                 return -1;
312
313         sprintf(aaa, "PAS2 %s", response);
314         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
315         free(aaa);
316         return ret;
317 }
318
319
320 /*
321  * Create a new user.  This returns 200 plus the same arguments as TryPassword
322  * if selfservice is nonzero, unless there was a problem creating the account.
323  * If selfservice is zero, creates a new user but does not log out the existing
324  * user - intended for use by system administrators to create accounts on
325  * behalf of other users.
326  */
327 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret)
328 {
329         int ret;
330         char *aaa;
331
332         if (!username)
333                 return -2;
334         if (!cret)
335                 return -2;
336
337         aaa = (char *) malloc((size_t) (strlen(username) + 6));
338         if (!aaa)
339                 return -1;
340
341         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
342         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
343         free(aaa);
344         return ret;
345 }
346
347
348 /*
349  * Changes the user's password.  Returns 200 if changed, errors otherwise.
350  */
351 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret)
352 {
353         int ret;
354         char *aaa;
355
356         if (!passwd)
357                 return -2;
358         if (!cret)
359                 return -2;
360
361         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
362         if (!aaa)
363                 return -1;
364
365         sprintf(aaa, "SETP %s", passwd);
366         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
367         free(aaa);
368         return ret;
369 }
370
371
372 /* LKRN */
373 /* Caller must free the march list */
374 /* Room types are defined in enum RoomList; keep these in sync! */
375 /* floor is -1 for all, or floornum */
376 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret)
377 {
378         int ret;
379         struct march *march = NULL;
380         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
381         char aaa[SIZ];
382         char *bbb = NULL;
383         size_t bbb_len;
384
385         if (!listing)
386                 return -2;
387         if (*listing)
388                 return -2;      /* Free the listing first */
389         if (!cret)
390                 return -2;
391         /* if (which < 0 || which > 4) return -2; */
392         if (floor < -1)
393                 return -2;      /* Can't validate upper bound, sorry */
394
395         sprintf(aaa, "%s %d", proto[which], floor);
396         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
397         if (ret / 100 == 1) {
398                 struct march *mptr;
399
400                 while (bbb && strlen(bbb)) {
401                         int a;
402
403                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
404                         a = strlen(aaa);
405                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
406                         mptr = (struct march *) malloc(sizeof(struct march));
407                         if (mptr) {
408                                 mptr->next = NULL;
409                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
410                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
411                                 mptr->march_floor = (char) extract_int(aaa, 2);
412                                 mptr->march_order = (char) extract_int(aaa, 3);
413                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
414                                 mptr->march_access = (char) extract_int(aaa, 5);
415                                 if (march == NULL)
416                                         march = mptr;
417                                 else {
418                                         struct march *mptr2;
419
420                                         mptr2 = march;
421                                         while (mptr2->next != NULL)
422                                                 mptr2 = mptr2->next;
423                                         mptr2->next = mptr;
424                                 }
425                         }
426                 }
427         }
428         *listing = march;
429         if (bbb)
430                 free(bbb);
431         return ret;
432 }
433
434
435 /* GETU */
436 /* Caller must free the struct ctdluser; caller may pass an existing one */
437 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret)
438 {
439         int ret;
440
441         if (!cret)
442                 return -2;
443         if (!uret)
444                 return -2;
445         if (!*uret)
446                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
447         if (!*uret)
448                 return -1;
449
450         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
451         if (ret / 100 == 2) {
452                 uret[0]->flags = extract_int(cret, 2);
453         }
454         return ret;
455 }
456
457
458 /* SETU */
459 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret)
460 {
461         char aaa[48];
462
463         if (!uret)
464                 return -2;
465         if (!cret)
466                 return -2;
467
468         sprintf(aaa, "SETU 80|24|%d", uret->flags);
469         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
470 }
471
472
473 /* RENU */
474 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret)
475 {
476         int ret;
477         char cmd[256];
478
479         if (!oldname)
480                 return -2;
481         if (!newname)
482                 return -2;
483         if (!cret)
484                 return -2;
485
486         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
487         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
488         return ret;
489 }
490
491
492 /* GOTO */
493 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret)
494 {
495         int ret;
496         char *aaa;
497
498         if (!cret)
499                 return -2;
500         if (!rret)
501                 return -2;
502         if (!*rret)
503                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
504         if (!*rret)
505                 return -1;
506
507         if (passwd) {
508                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
509                 if (!aaa) {
510                         free(*rret);
511                         return -1;
512                 }
513                 sprintf(aaa, "GOTO %s|%s", room, passwd);
514         } else {
515                 aaa = (char *) malloc(strlen(room) + 6);
516                 if (!aaa) {
517                         free(*rret);
518                         return -1;
519                 }
520                 sprintf(aaa, "GOTO %s", room);
521         }
522         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
523         if (ret / 100 == 2) {
524                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
525                 rret[0]->RRunread = extract_long(cret, 1);
526                 rret[0]->RRtotal = extract_long(cret, 2);
527                 rret[0]->RRinfoupdated = extract_int(cret, 3);
528                 rret[0]->RRflags = extract_int(cret, 4);
529                 rret[0]->RRhighest = extract_long(cret, 5);
530                 rret[0]->RRlastread = extract_long(cret, 6);
531                 rret[0]->RRismailbox = extract_int(cret, 7);
532                 rret[0]->RRaide = extract_int(cret, 8);
533                 rret[0]->RRnewmail = extract_long(cret, 9);
534                 rret[0]->RRfloor = extract_int(cret, 10);
535                 rret[0]->RRcurrentview = extract_int(cret, 11);
536                 rret[0]->RRdefaultview = extract_int(cret, 12);
537                 /* position 13 is a trash folder flag ... irrelevant in this client */
538                 rret[0]->RRflags2 = extract_int(cret, 14);
539         } else {
540                 free(*rret);
541                 *rret = NULL;
542         }
543         free(aaa);
544         return ret;
545 }
546
547
548 /* MSGS */
549 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
550 /* whicharg is number of messages, applies to last, first, gt, lt */
551 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret)
552 {
553         int ret;
554         unsigned long count = 0;
555         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
556         char aaa[33];
557         char *bbb = NULL;
558         size_t bbb_len;
559
560         if (!cret)
561                 return -2;
562         if (!mret)
563                 return -2;
564         if (*mret)
565                 return -2;
566         if (which < 0 || which > 6)
567                 return -2;
568
569         if (which <= 2)
570                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
571         else
572                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
573         if (mtemplate)
574                 count = strlen(mtemplate);
575         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
576         if (ret / 100 != 1)
577                 return ret;
578         count = 0;
579         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
580         if (!*mret)
581                 return -1;
582         while (bbb && strlen(bbb)) {
583                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
584                 remove_token(bbb, 0, '\n');
585                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
586                 if (*mret) {
587                         (*mret)[count++] = atol(aaa);
588                         (*mret)[count] = 0L;
589                 } else {
590                         break;
591                 }
592         }
593         if (bbb)
594                 free(bbb);
595         return ret;
596 }
597
598
599 /* MSG0, MSG2 */
600 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret)
601 {
602         int ret;
603         char aaa[SIZ];
604         char *bbb = NULL;
605         size_t bbb_len;
606         int multipart_hunting = 0;
607         char multipart_prefix[128];
608         char encoding[256];
609
610         if (!cret)
611                 return -1;
612         if (!mret)
613                 return -1;
614         if (!*mret)
615                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
616         if (!*mret)
617                 return -1;
618         if (!msgnum)
619                 return -1;
620
621         strcpy(encoding, "");
622         strcpy(mret[0]->content_type, "");
623         mret[0]->is_local = 0;
624         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
625         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
626         if (ret / 100 == 1) {
627                 if (as_mime != 2) {
628                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
629                         while (strlen(bbb) > 4 && bbb[4] == '=') {
630                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
631                                 remove_token(bbb, 0, '\n');
632
633                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
634                                         mret[0]->nhdr = 1;
635                                 else if (!strncasecmp(aaa, "from=", 5))
636                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
637                                 else if (!strncasecmp(aaa, "type=", 5))
638                                         mret[0]->type = atoi(&aaa[5]);
639                                 else if (!strncasecmp(aaa, "msgn=", 5))
640                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
641                                 else if (!strncasecmp(aaa, "subj=", 5))
642                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
643                                 else if (!strncasecmp(aaa, "rfca=", 5))
644                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
645                                 else if (!strncasecmp(aaa, "room=", 5))
646                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
647                                 else if (!strncasecmp(aaa, "rcpt=", 5))
648                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
649                                 else if (!strncasecmp(aaa, "wefw=", 5))
650                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
651                                 else if (!strncasecmp(aaa, "time=", 5))
652                                         mret[0]->time = atol(&aaa[5]);
653                                 else if (!strncasecmp(aaa, "locl", 4))
654                                         mret[0]->is_local = 1;
655
656                                 /* Multipart/alternative prefix & suffix strings help
657                                  * us to determine which part we want to download.
658                                  */
659                                 else if (!strncasecmp(aaa, "pref=", 5)) {
660                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
661                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
662                                                 ++multipart_hunting;
663                                         }
664                                 } else if (!strncasecmp(aaa, "suff=", 5)) {
665                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
666                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
667                                                 ++multipart_hunting;
668                                         }
669                                 }
670
671                                 else if (!strncasecmp(aaa, "part=", 5)) {
672                                         struct parts *ptr, *chain;
673
674                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
675                                         if (ptr) {
676
677                                                 /* Fill the buffers for the caller */
678                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
679                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
680                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
681                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
682                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
683                                                 ptr->length = extract_long(&aaa[5], 5);
684                                                 if (!mret[0]->attachments)
685                                                         mret[0]->attachments = ptr;
686                                                 else {
687                                                         chain = mret[0]->attachments;
688                                                         while (chain->next)
689                                                                 chain = chain->next;
690                                                         chain->next = ptr;
691                                                 }
692
693                                                 /* Now handle multipart/alternative */
694                                                 if (multipart_hunting > 0) {
695                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
696                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
697                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
698                                                         }
699                                                 }
700
701                                         }
702                                 }
703                         }
704                         /* Eliminate "text\n" */
705                         remove_token(bbb, 0, '\n');
706
707                         /* If doing a MIME thing, pull out the extra headers */
708                         if (as_mime == 4) {
709                                 do {
710                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
711                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
712                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
713                                                 striplt(mret[0]->content_type);
714
715                                                 /* strip out ";charset=" portion.  FIXME do something with
716                                                  * the charset (like... convert it) instead of just throwing
717                                                  * it away
718                                                  */
719                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
720                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
721                                                 }
722
723                                         }
724                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
725                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
726                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
727                                                 striplt(mret[0]->mime_chosen);
728                                         }
729                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
730                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
731                                                 strcpy(encoding, &encoding[26]);
732                                                 striplt(encoding);
733                                         }
734                                         remove_token(bbb, 0, '\n');
735                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
736                                 remove_token(bbb, 0, '\n');
737                         }
738
739
740                 }
741                 if (strlen(bbb)) {
742
743                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
744                                 char *ccc = NULL;
745                                 int bytes_decoded = 0;
746                                 ccc = malloc(strlen(bbb) + 32768);
747                                 if (!strcasecmp(encoding, "base64")) {
748                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
749                                 } else if (!strcasecmp(encoding, "quoted-printable")) {
750                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
751                                 }
752                                 ccc[bytes_decoded] = 0;
753                                 free(bbb);
754                                 bbb = ccc;
755                         }
756
757                         /* FIXME: Strip trailing whitespace */
758                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
759
760                 } else {
761                         bbb = (char *) realloc(bbb, 1);
762                         *bbb = '\0';
763                 }
764                 mret[0]->text = bbb;
765         }
766         return ret;
767 }
768
769
770 /* WHOK */
771 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret)
772 {
773         int ret;
774         size_t bytes;
775
776         if (!cret)
777                 return -2;
778         if (!listing)
779                 return -2;
780         if (*listing)
781                 return -2;
782
783         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
784         return ret;
785 }
786
787
788 /* INFO */
789 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret)
790 {
791         int ret;
792         size_t bytes;
793         char *listing = NULL;
794         char buf[SIZ];
795
796         if (!cret)
797                 return -2;
798
799         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
800         if (ret / 100 == 1) {
801                 int line = 0;
802
803                 while (*listing && strlen(listing)) {
804                         extract_token(buf, listing, 0, '\n', sizeof buf);
805                         remove_token(listing, 0, '\n');
806                         switch (line++) {
807                         case 0:
808                                 ipc->ServInfo.pid = atoi(buf);
809                                 break;
810                         case 1:
811                                 strcpy(ipc->ServInfo.nodename, buf);
812                                 break;
813                         case 2:
814                                 strcpy(ipc->ServInfo.humannode, buf);
815                                 break;
816                         case 3:
817                                 strcpy(ipc->ServInfo.fqdn, buf);
818                                 break;
819                         case 4:
820                                 strcpy(ipc->ServInfo.software, buf);
821                                 break;
822                         case 5:
823                                 ipc->ServInfo.rev_level = atoi(buf);
824                                 break;
825                         case 6:
826                                 strcpy(ipc->ServInfo.site_location, buf);
827                                 break;
828                         case 7:
829                                 strcpy(ipc->ServInfo.sysadm, buf);
830                                 break;
831                         case 9:
832                                 strcpy(ipc->ServInfo.moreprompt, buf);
833                                 break;
834                         case 10:
835                                 ipc->ServInfo.ok_floors = atoi(buf);
836                                 break;
837                         case 11:
838                                 ipc->ServInfo.paging_level = atoi(buf);
839                                 break;
840                         case 13:
841                                 ipc->ServInfo.supports_qnop = atoi(buf);
842                                 break;
843                         case 14:
844                                 ipc->ServInfo.supports_ldap = atoi(buf);
845                                 break;
846                         case 15:
847                                 ipc->ServInfo.newuser_disabled = atoi(buf);
848                                 break;
849                         case 16:
850                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
851                                 break;
852                         case 17:
853                                 ipc->ServInfo.load_avg = atof(buf);
854                                 break;
855                         case 18:
856                                 ipc->ServInfo.worker_avg = atof(buf);
857                                 break;
858                         case 19:
859                                 ipc->ServInfo.thread_count = atoi(buf);
860                                 break;
861                         case 20:
862                                 ipc->ServInfo.has_sieve = atoi(buf);
863                                 break;
864                         case 21:
865                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
866                                 break;
867                         case 22:
868                                 strcpy(ipc->ServInfo.svn_revision, buf);
869                                 break;
870                         case 24:
871                                 ipc->ServInfo.guest_logins = atoi(buf);
872                                 break;
873                         }
874                 }
875
876         }
877         if (listing)
878                 free(listing);
879         return ret;
880 }
881
882
883 /* RDIR */
884 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret)
885 {
886         int ret;
887         size_t bytes;
888
889         if (!cret)
890                 return -2;
891         if (!listing)
892                 return -2;
893         if (*listing)
894                 return -2;
895
896         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
897         return ret;
898 }
899
900
901 /*
902  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
903  */
904 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret)
905 {
906         int ret;
907         char aaa[64];
908
909         if (!cret)
910                 return -2;
911
912         if (msgnum) {
913                 sprintf(aaa, "SLRP %ld", msgnum);
914         } else {
915                 sprintf(aaa, "SLRP HIGHEST");
916         }
917         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
918         return ret;
919 }
920
921
922 /* INVT */
923 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret)
924 {
925         int ret;
926         char *aaa;
927
928         if (!cret)
929                 return -2;
930         if (!username)
931                 return -2;
932
933         aaa = (char *) malloc(strlen(username) + 6);
934         if (!aaa)
935                 return -1;
936
937         sprintf(aaa, "INVT %s", username);
938         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
939         free(aaa);
940         return ret;
941 }
942
943
944 /* KICK */
945 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret)
946 {
947         int ret;
948         char *aaa;
949
950         if (!cret)
951                 return -1;
952         if (!username)
953                 return -1;
954
955         aaa = (char *) malloc(strlen(username) + 6);
956
957         sprintf(aaa, "KICK %s", username);
958         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
959         free(aaa);
960         return ret;
961 }
962
963
964 /* GETR */
965 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret)
966 {
967         int ret;
968
969         if (!cret)
970                 return -2;
971         if (!qret)
972                 return -2;
973         if (!*qret)
974                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
975         if (!*qret)
976                 return -1;
977
978         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
979         if (ret / 100 == 2) {
980                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
981                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
982                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
983                 qret[0]->QRflags = extract_int(cret, 3);
984                 qret[0]->QRfloor = extract_int(cret, 4);
985                 qret[0]->QRorder = extract_int(cret, 5);
986                 qret[0]->QRdefaultview = extract_int(cret, 6);
987                 qret[0]->QRflags2 = extract_int(cret, 7);
988         }
989         return ret;
990 }
991
992
993 /* SETR */
994 /* set forget to kick all users out of room */
995 int CtdlIPCSetRoomAttributes(CtdlIPC * ipc, int forget, struct ctdlroom *qret, char *cret)
996 {
997         int ret;
998         char *aaa;
999
1000         if (!cret)
1001                 return -2;
1002         if (!qret)
1003                 return -2;
1004
1005         aaa = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
1006         if (!aaa)
1007                 return -1;
1008
1009         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1010                 qret->QRname, qret->QRpasswd, qret->QRdirname,
1011                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
1012         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1013         free(aaa);
1014         return ret;
1015 }
1016
1017
1018 /* GETA */
1019 int CtdlIPCGetRoomAide(CtdlIPC * ipc, char *cret)
1020 {
1021         if (!cret)
1022                 return -1;
1023
1024         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1025 }
1026
1027
1028 /* SETA */
1029 int CtdlIPCSetRoomAide(CtdlIPC * ipc, const char *username, char *cret)
1030 {
1031         int ret;
1032         char *aaa;
1033
1034         if (!cret)
1035                 return -2;
1036         if (!username)
1037                 return -2;
1038
1039         aaa = (char *) malloc(strlen(username) + 6);
1040         if (!aaa)
1041                 return -1;
1042
1043         sprintf(aaa, "SETA %s", username);
1044         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1045         free(aaa);
1046         return ret;
1047 }
1048
1049
1050 /* ENT0 */
1051 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1052 {
1053         int ret;
1054         char cmd[SIZ];
1055         char *ptr;
1056
1057         if (!cret)
1058                 return -2;
1059         if (!mr)
1060                 return -2;
1061
1062         if (mr->references) {
1063                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1064                         if (*ptr == '|')
1065                                 *ptr = '!';
1066                 }
1067         }
1068
1069         snprintf(cmd, sizeof cmd,
1070                  "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1071                  mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1072         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL, NULL, cret);
1073         if ((flag == 0) && (subject_required != NULL)) {
1074                 /* Is the server strongly recommending that the user enter a message subject? */
1075                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1076                         *subject_required = extract_int(&cret[4], 1);
1077                 }
1078
1079
1080         }
1081         return ret;
1082 }
1083
1084
1085 /* RINF */
1086 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret)
1087 {
1088         size_t bytes;
1089
1090         if (!cret)
1091                 return -2;
1092         if (!iret)
1093                 return -2;
1094         if (*iret)
1095                 return -2;
1096
1097         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1098 }
1099
1100
1101 /* DELE */
1102 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret)
1103 {
1104         char aaa[64];
1105
1106         if (!cret)
1107                 return -2;
1108         if (!msgnum)
1109                 return -2;
1110
1111         sprintf(aaa, "DELE %ld", msgnum);
1112         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1113 }
1114
1115
1116 /* MOVE */
1117 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret)
1118 {
1119         int ret;
1120         char *aaa;
1121
1122         if (!cret)
1123                 return -2;
1124         if (!destroom)
1125                 return -2;
1126         if (!msgnum)
1127                 return -2;
1128
1129         aaa = (char *) malloc(strlen(destroom) + 28);
1130         if (!aaa)
1131                 return -1;
1132
1133         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1134         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1135         free(aaa);
1136         return ret;
1137 }
1138
1139
1140 /* KILL */
1141 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret)
1142 {
1143         char aaa[64];
1144
1145         if (!cret)
1146                 return -2;
1147
1148         sprintf(aaa, "KILL %d", for_real);
1149         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1150 }
1151
1152
1153 /* CRE8 */
1154 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret)
1155 {
1156         int ret;
1157         char *aaa;
1158
1159         if (!cret)
1160                 return -2;
1161         if (!roomname)
1162                 return -2;
1163
1164         if (password) {
1165                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1166                 if (!aaa)
1167                         return -1;
1168                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1169         } else {
1170                 aaa = (char *) malloc(strlen(roomname) + 40);
1171                 if (!aaa)
1172                         return -1;
1173                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1174         }
1175         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1176         free(aaa);
1177         return ret;
1178 }
1179
1180
1181 /* FORG */
1182 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret)
1183 {
1184         if (!cret)
1185                 return -2;
1186
1187         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1188 }
1189
1190
1191 /* MESG */
1192 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret)
1193 {
1194         int ret;
1195         char *aaa;
1196         size_t bytes;
1197
1198         if (!cret)
1199                 return -2;
1200         if (!mret)
1201                 return -2;
1202         if (*mret)
1203                 return -2;
1204         if (!message)
1205                 return -2;
1206
1207         aaa = (char *) malloc(strlen(message) + 6);
1208         if (!aaa)
1209                 return -1;
1210
1211         sprintf(aaa, "MESG %s", message);
1212         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1213         free(aaa);
1214         return ret;
1215 }
1216
1217
1218 /* GNUR */
1219 int CtdlIPCNextUnvalidatedUser(CtdlIPC * ipc, char *cret)
1220 {
1221         if (!cret)
1222                 return -2;
1223
1224         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1225 }
1226
1227
1228 /* GREG */
1229 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret)
1230 {
1231         int ret;
1232         char *aaa;
1233         size_t bytes;
1234
1235         if (!cret)
1236                 return -2;
1237         if (!rret)
1238                 return -2;
1239         if (*rret)
1240                 return -2;
1241
1242         if (username)
1243                 aaa = (char *) malloc(strlen(username) + 6);
1244         else
1245                 aaa = (char *) malloc(12);
1246         if (!aaa)
1247                 return -1;
1248
1249         if (username)
1250                 sprintf(aaa, "GREG %s", username);
1251         else
1252                 sprintf(aaa, "GREG _SELF_");
1253         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1254         free(aaa);
1255         return ret;
1256 }
1257
1258
1259 /* VALI */
1260 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret)
1261 {
1262         int ret;
1263         char *aaa;
1264
1265         if (!cret)
1266                 return -2;
1267         if (!username)
1268                 return -2;
1269         if (axlevel < AxDeleted || axlevel > AxAideU)
1270                 return -2;
1271
1272         aaa = (char *) malloc(strlen(username) + 17);
1273         if (!aaa)
1274                 return -1;
1275
1276         sprintf(aaa, "VALI %s|%d", username, axlevel);
1277         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1278         free(aaa);
1279         return ret;
1280 }
1281
1282
1283 /* EINF */
1284 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret)
1285 {
1286         char aaa[64];
1287
1288         if (!cret)
1289                 return -1;
1290         if (!info)
1291                 return -1;
1292
1293         sprintf(aaa, "EINF %d", for_real);
1294         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1295 }
1296
1297
1298 /* LIST */
1299 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret)
1300 {
1301         size_t bytes;
1302         char *cmd;
1303         int ret;
1304
1305         if (!cret)
1306                 return -1;
1307         if (!listing)
1308                 return -1;
1309         if (*listing)
1310                 return -1;
1311         if (!searchstring)
1312                 return -1;
1313
1314         cmd = malloc(strlen(searchstring) + 10);
1315         sprintf(cmd, "LIST %s", searchstring);
1316
1317         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1318         free(cmd);
1319         return (ret);
1320 }
1321
1322
1323 /* REGI */
1324 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret)
1325 {
1326         if (!cret)
1327                 return -1;
1328         if (!info)
1329                 return -1;
1330
1331         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1332 }
1333
1334
1335 /* CHEK */
1336 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret)
1337 {
1338         int ret;
1339
1340         if (!cret)
1341                 return -1;
1342         if (!chek)
1343                 return -1;
1344
1345         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1346         if (ret / 100 == 2) {
1347                 chek->newmail = extract_long(cret, 0);
1348                 chek->needregis = extract_int(cret, 1);
1349                 chek->needvalid = extract_int(cret, 2);
1350         }
1351         return ret;
1352 }
1353
1354
1355 /* DELF */
1356 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret)
1357 {
1358         int ret;
1359         char *aaa;
1360
1361         if (!cret)
1362                 return -2;
1363         if (!filename)
1364                 return -2;
1365
1366         aaa = (char *) malloc(strlen(filename) + 6);
1367         if (!aaa)
1368                 return -1;
1369
1370         sprintf(aaa, "DELF %s", filename);
1371         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1372         free(aaa);
1373         return ret;
1374 }
1375
1376
1377 /* MOVF */
1378 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret)
1379 {
1380         int ret;
1381         char *aaa;
1382
1383         if (!cret)
1384                 return -2;
1385         if (!filename)
1386                 return -2;
1387         if (!destroom)
1388                 return -2;
1389
1390         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1391         if (!aaa)
1392                 return -1;
1393
1394         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1395         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1396         free(aaa);
1397         return ret;
1398 }
1399
1400
1401 /* RWHO */
1402 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret)
1403 {
1404         int ret;
1405         size_t bytes;
1406
1407         if (!cret)
1408                 return -1;
1409         if (!listing)
1410                 return -1;
1411         if (*listing)
1412                 return -1;
1413
1414         *stamp = CtdlIPCServerTime(ipc, cret);
1415         if (!*stamp)
1416                 *stamp = time(NULL);
1417         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1418         return ret;
1419 }
1420
1421
1422 /* OPEN */
1423 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1424                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
1425 {
1426         int ret;
1427         size_t bytes;
1428         time_t last_mod;
1429         char mimetype[SIZ];
1430         char *aaa;
1431
1432         if (!cret)
1433                 return -2;
1434         if (!filename)
1435                 return -2;
1436         if (!buf)
1437                 return -2;
1438         if (*buf)
1439                 return -2;
1440         if (ipc->downloading)
1441                 return -2;
1442
1443         aaa = (char *) malloc(strlen(filename) + 6);
1444         if (!aaa)
1445                 return -1;
1446
1447         sprintf(aaa, "OPEN %s", filename);
1448         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1449         free(aaa);
1450         if (ret / 100 == 2) {
1451                 ipc->downloading = 1;
1452                 bytes = extract_long(cret, 0);
1453                 last_mod = extract_int(cret, 1);
1454                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1455
1456                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1457                 /*
1458                    ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1459                    progress_gauge_callback, cret);
1460                  */
1461
1462                 ret = CtdlIPCEndDownload(ipc, cret);
1463                 if (ret / 100 == 2)
1464                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1465         }
1466         return ret;
1467 }
1468
1469
1470 /* OPNA */
1471 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1472                                (CtdlIPC *, unsigned long, unsigned long), char *cret)
1473 {
1474         int ret;
1475         size_t bytes;
1476         time_t last_mod;
1477         char filename[SIZ];
1478         char mimetype[SIZ];
1479         char aaa[SIZ];
1480
1481         if (!cret)
1482                 return -2;
1483         if (!buf)
1484                 return -2;
1485         if (*buf)
1486                 return -2;
1487         if (!part)
1488                 return -2;
1489         if (!msgnum)
1490                 return -2;
1491         if (ipc->downloading)
1492                 return -2;
1493
1494         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1495         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1496         if (ret / 100 == 2) {
1497                 ipc->downloading = 1;
1498                 bytes = extract_long(cret, 0);
1499                 last_mod = extract_int(cret, 1);
1500                 extract_token(filename, cret, 2, '|', sizeof filename);
1501                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1502                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1503                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1504                 ret = CtdlIPCEndDownload(ipc, cret);
1505                 if (ret / 100 == 2)
1506                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1507         }
1508         return ret;
1509 }
1510
1511
1512 /* OIMG */
1513 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1514                           (CtdlIPC *, unsigned long, unsigned long), char *cret)
1515 {
1516         int ret;
1517         size_t bytes;
1518         time_t last_mod;
1519         char mimetype[SIZ];
1520         char *aaa;
1521
1522         if (!cret)
1523                 return -1;
1524         if (!buf)
1525                 return -1;
1526         if (*buf)
1527                 return -1;
1528         if (!filename)
1529                 return -1;
1530         if (ipc->downloading)
1531                 return -1;
1532
1533         aaa = (char *) malloc(strlen(filename) + 6);
1534         if (!aaa)
1535                 return -1;
1536
1537         sprintf(aaa, "OIMG %s", filename);
1538         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1539         free(aaa);
1540         if (ret / 100 == 2) {
1541                 ipc->downloading = 1;
1542                 bytes = extract_long(cret, 0);
1543                 last_mod = extract_int(cret, 1);
1544                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1545 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1546                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1547                 ret = CtdlIPCEndDownload(ipc, cret);
1548                 if (ret / 100 == 2)
1549                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1550         }
1551         return ret;
1552 }
1553
1554
1555 /* UOPN */
1556 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1557                        (CtdlIPC *, unsigned long, unsigned long), char *cret)
1558 {
1559         int ret;
1560         char *aaa;
1561         FILE *uploadFP;
1562         char MimeTestBuf[64];
1563         const char *MimeType;
1564         long len;
1565
1566         if (!cret)
1567                 return -1;
1568         if (!save_as)
1569                 return -1;
1570         if (!comment)
1571                 return -1;
1572         if (!path)
1573                 return -1;
1574         if (!*path)
1575                 return -1;
1576         if (ipc->uploading)
1577                 return -1;
1578
1579         uploadFP = fopen(path, "r");
1580         if (!uploadFP)
1581                 return -2;
1582
1583         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1584         rewind(uploadFP);
1585         if (len < 0)
1586                 return -3;
1587
1588         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1589         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1590         if (!aaa)
1591                 return -1;
1592
1593         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1594         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1595         free(aaa);
1596         if (ret / 100 == 2) {
1597                 ipc->uploading = 1;
1598                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1599                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1600                 ipc->uploading = 0;
1601         }
1602         return ret;
1603 }
1604
1605
1606 /* UIMG */
1607 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1608                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
1609 {
1610         int ret;
1611         FILE *uploadFP;
1612         char *aaa;
1613         char MimeTestBuf[64];
1614         const char *MimeType;
1615         long len;
1616
1617         if (!cret)
1618                 return -1;
1619         if (!save_as)
1620                 return -1;
1621         if (!path && for_real)
1622                 return -1;
1623         if (!*path && for_real)
1624                 return -1;
1625         if (ipc->uploading)
1626                 return -1;
1627
1628         aaa = (char *) malloc(strlen(save_as) + 17);
1629         if (!aaa)
1630                 return -1;
1631
1632         uploadFP = fopen(path, "r");
1633         if (!uploadFP)
1634                 return -2;
1635
1636         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1637         rewind(uploadFP);
1638         if (len < 0)
1639                 return -3;
1640         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1641
1642         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1643         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1644         free(aaa);
1645         if (ret / 100 == 2 && for_real) {
1646                 ipc->uploading = 1;
1647                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1648                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1649                 ipc->uploading = 0;
1650         }
1651         return ret;
1652 }
1653
1654
1655 /* QUSR */
1656 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret)
1657 {
1658         int ret;
1659         char *aaa;
1660
1661         if (!cret)
1662                 return -2;
1663         if (!username)
1664                 return -2;
1665
1666         aaa = (char *) malloc(strlen(username) + 6);
1667         if (!aaa)
1668                 return -1;
1669
1670         sprintf(aaa, "QUSR %s", username);
1671         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1672         free(aaa);
1673         return ret;
1674 }
1675
1676
1677 /* LFLR */
1678 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret)
1679 {
1680         size_t bytes;
1681
1682         if (!cret)
1683                 return -2;
1684         if (!listing)
1685                 return -2;
1686         if (*listing)
1687                 return -2;
1688
1689         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1690 }
1691
1692
1693 /* CFLR */
1694 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret)
1695 {
1696         int ret;
1697         char aaa[SIZ];
1698
1699         if (!cret)
1700                 return -2;
1701         if (!name)
1702                 return -2;
1703
1704         sprintf(aaa, "CFLR %s|%d", name, for_real);
1705         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1706         return ret;
1707 }
1708
1709
1710 /* KFLR */
1711 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret)
1712 {
1713         char aaa[SIZ];
1714
1715         if (!cret)
1716                 return -1;
1717         if (floornum < 0)
1718                 return -1;
1719
1720         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1721         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1722 }
1723
1724
1725 /* EFLR */
1726 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret)
1727 {
1728         int ret;
1729         char aaa[SIZ];
1730
1731         if (!cret)
1732                 return -2;
1733         if (!floorname)
1734                 return -2;
1735         if (floornum < 0)
1736                 return -2;
1737
1738         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1739         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1740         return ret;
1741 }
1742
1743
1744 /*
1745  * IDEN 
1746  *
1747  * You only need to fill out hostname, the defaults will be used if any of the
1748  * other fields are not set properly.
1749  */
1750 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1751                             int revision, const char *software_name, const char *hostname, char *cret)
1752 {
1753         int ret;
1754         char *aaa;
1755
1756         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1757                 developerid = 8;
1758                 clientid = 0;
1759                 revision = CLIENT_VERSION - 600;
1760                 software_name = "Citadel (libcitadel)";
1761         }
1762         if (!hostname)
1763                 return -2;
1764
1765         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1766         if (!aaa)
1767                 return -1;
1768
1769         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1770         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1771         free(aaa);
1772         return ret;
1773 }
1774
1775
1776 /* SEXP */
1777 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret)
1778 {
1779         int ret;
1780         char *aaa;
1781
1782         if (!cret)
1783                 return -2;
1784         if (!username)
1785                 return -2;
1786
1787         aaa = (char *) malloc(strlen(username) + 8);
1788         if (!aaa)
1789                 return -1;
1790
1791         if (text) {
1792                 sprintf(aaa, "SEXP %s|-", username);
1793                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1794         } else {
1795                 sprintf(aaa, "SEXP %s||", username);
1796                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1797         }
1798         free(aaa);
1799         return ret;
1800 }
1801
1802
1803 /* GEXP */
1804 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret)
1805 {
1806         size_t bytes;
1807
1808         if (!cret)
1809                 return -2;
1810         if (!listing)
1811                 return -2;
1812         if (*listing)
1813                 return -2;
1814
1815         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1816 }
1817
1818
1819 /* DEXP */
1820 /* mode is 0 = enable, 1 = disable, 2 = status */
1821 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret)
1822 {
1823         char aaa[64];
1824
1825         if (!cret)
1826                 return -2;
1827
1828         sprintf(aaa, "DEXP %d", mode);
1829         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1830 }
1831
1832
1833 /* EBIO */
1834 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret)
1835 {
1836         if (!cret)
1837                 return -2;
1838         if (!bio)
1839                 return -2;
1840
1841         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1842 }
1843
1844
1845 /* RBIO */
1846 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret)
1847 {
1848         int ret;
1849         size_t bytes;
1850         char *aaa;
1851
1852         if (!cret)
1853                 return -2;
1854         if (!username)
1855                 return -2;
1856         if (!listing)
1857                 return -2;
1858         if (*listing)
1859                 return -2;
1860
1861         aaa = (char *) malloc(strlen(username) + 6);
1862         if (!aaa)
1863                 return -1;
1864
1865         sprintf(aaa, "RBIO %s", username);
1866         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1867         free(aaa);
1868         return ret;
1869 }
1870
1871
1872 /* LBIO */
1873 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret)
1874 {
1875         size_t bytes;
1876
1877         if (!cret)
1878                 return -2;
1879         if (!listing)
1880                 return -2;
1881         if (*listing)
1882                 return -2;
1883
1884         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1885 }
1886
1887
1888 /* STEL */
1889 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret)
1890 {
1891         char aaa[64];
1892
1893         if (!cret)
1894                 return -1;
1895
1896         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1897         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1898 }
1899
1900
1901 /* TERM */
1902 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret)
1903 {
1904         char aaa[64];
1905
1906         if (!cret)
1907                 return -1;
1908
1909         sprintf(aaa, "TERM %d", sid);
1910         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1911 }
1912
1913
1914 /* DOWN */
1915 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret)
1916 {
1917         if (!cret)
1918                 return -1;
1919
1920         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1921 }
1922
1923
1924 /* SCDN */
1925 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret)
1926 {
1927         char aaa[16];
1928
1929         if (!cret)
1930                 return -1;
1931
1932         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1933         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1934 }
1935
1936
1937 /* EMSG */
1938 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret)
1939 {
1940         int ret;
1941         char *aaa;
1942
1943         if (!cret)
1944                 return -2;
1945         if (!text)
1946                 return -2;
1947         if (!filename)
1948                 return -2;
1949
1950         aaa = (char *) malloc(strlen(filename) + 6);
1951         if (!aaa)
1952                 return -1;
1953
1954         sprintf(aaa, "EMSG %s", filename);
1955         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1956         free(aaa);
1957         return ret;
1958 }
1959
1960
1961 /* TIME */
1962 /* This function returns the actual server time reported, or 0 if error */
1963 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret)
1964 {
1965         time_t tret;
1966         int ret;
1967
1968         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1969         if (ret / 100 == 2) {
1970                 tret = extract_long(cret, 0);
1971         } else {
1972                 tret = 0L;
1973         }
1974         return tret;
1975 }
1976
1977
1978 /* AGUP */
1979 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret)
1980 {
1981         int ret;
1982         char aaa[SIZ];
1983
1984         if (!cret)
1985                 return -2;
1986         if (!uret)
1987                 return -2;
1988         if (!*uret)
1989                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
1990         if (!*uret)
1991                 return -1;
1992
1993         sprintf(aaa, "AGUP %s", who);
1994         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1995
1996         if (ret / 100 == 2) {
1997                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1998                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1999                 uret[0]->flags = extract_int(cret, 2);
2000                 uret[0]->timescalled = extract_long(cret, 3);
2001                 uret[0]->posted = extract_long(cret, 4);
2002                 uret[0]->axlevel = extract_int(cret, 5);
2003                 uret[0]->usernum = extract_long(cret, 6);
2004                 uret[0]->lastcall = extract_long(cret, 7);
2005                 uret[0]->USuserpurge = extract_int(cret, 8);
2006         }
2007         return ret;
2008 }
2009
2010
2011 /* ASUP */
2012 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret)
2013 {
2014         int ret;
2015         char *aaa;
2016
2017         if (!cret)
2018                 return -2;
2019         if (!uret)
2020                 return -2;
2021
2022         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
2023         if (!aaa)
2024                 return -1;
2025
2026         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
2027                 uret->fullname, uret->password, uret->flags, uret->timescalled,
2028                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
2029         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2030         free(aaa);
2031         return ret;
2032 }
2033
2034
2035 /* AGEA */
2036 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret)
2037 {
2038         int ret;
2039         char aaa[SIZ];
2040         char *emailaddrs = NULL;
2041         size_t emailaddrs_len = 0;
2042
2043         sprintf(aaa, "AGEA %s", who);
2044         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
2045
2046         if (ret / 100 == 1) {
2047                 strcpy(target_buf, emailaddrs);
2048         }
2049
2050         if (emailaddrs != NULL) {
2051                 free(emailaddrs);
2052         }
2053
2054         return ret;
2055 }
2056
2057
2058 /* ASEA */
2059 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret)
2060 {
2061         char aaa[SIZ];
2062         int ret;
2063
2064         if (!who)
2065                 return -2;
2066         if (!emailaddrs)
2067                 return -2;
2068         if (!cret)
2069                 return -2;
2070
2071         sprintf(aaa, "ASEA %s", who);
2072         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2073         return ret;
2074 }
2075
2076
2077 /* GPEX */
2078 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2079 /* caller must free the struct ExpirePolicy */
2080 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret)
2081 {
2082         static char *proto[] = {
2083                 strof(roompolicy),
2084                 strof(floorpolicy),
2085                 strof(sitepolicy),
2086                 strof(mailboxespolicy)
2087         };
2088         char cmd[256];
2089         int ret;
2090
2091         if (!cret)
2092                 return -2;
2093         if (!policy)
2094                 return -2;
2095         if (!*policy)
2096                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2097         if (!*policy)
2098                 return -1;
2099         if (which < 0 || which > 3)
2100                 return -2;
2101
2102         sprintf(cmd, "GPEX %s", proto[which]);
2103         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2104         if (ret / 100 == 2) {
2105                 policy[0]->expire_mode = extract_int(cret, 0);
2106                 policy[0]->expire_value = extract_int(cret, 1);
2107         }
2108         return ret;
2109 }
2110
2111
2112 /* SPEX */
2113 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2114 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2115 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret)
2116 {
2117         char aaa[38];
2118         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2119
2120         if (!cret)
2121                 return -2;
2122         if (which < 0 || which > 3)
2123                 return -2;
2124         if (!policy)
2125                 return -2;
2126         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2127                 return -2;
2128         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2129                 return -2;
2130
2131         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2132         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2133 }
2134
2135
2136 /* CONF GET */
2137 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret)
2138 {
2139         size_t bytes;
2140
2141         if (!cret)
2142                 return -2;
2143         if (!listing)
2144                 return -2;
2145         if (*listing)
2146                 return -2;
2147
2148         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2149 }
2150
2151
2152 /* CONF SET */
2153 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret)
2154 {
2155         if (!cret)
2156                 return -2;
2157         if (!listing)
2158                 return -2;
2159
2160         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2161 }
2162
2163
2164 /* CONF GETSYS */
2165 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret)
2166 {
2167         int ret;
2168         char *aaa;
2169         size_t bytes;
2170
2171         if (!cret)
2172                 return -2;
2173         if (!mimetype)
2174                 return -2;
2175         if (!listing)
2176                 return -2;
2177         if (*listing)
2178                 return -2;
2179
2180         aaa = malloc(strlen(mimetype) + 13);
2181         if (!aaa)
2182                 return -1;
2183         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2184         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2185         free(aaa);
2186         return ret;
2187 }
2188
2189
2190 /* CONF PUTSYS */
2191 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret)
2192 {
2193         int ret;
2194         char *aaa;
2195
2196         if (!cret)
2197                 return -2;
2198         if (!mimetype)
2199                 return -2;
2200         if (!listing)
2201                 return -2;
2202
2203         aaa = malloc(strlen(mimetype) + 13);
2204         if (!aaa)
2205                 return -1;
2206         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2207         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2208         free(aaa);
2209         return ret;
2210 }
2211
2212
2213 /* GNET */
2214 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret)
2215 {
2216         size_t bytes;
2217
2218         if (!cret)
2219                 return -2;
2220         if (!listing)
2221                 return -2;
2222         if (*listing)
2223                 return -2;
2224
2225         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2226 }
2227
2228
2229 /* SNET */
2230 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret)
2231 {
2232         if (!cret)
2233                 return -2;
2234         if (!listing)
2235                 return -2;
2236
2237         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2238 }
2239
2240
2241 /* REQT */
2242 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret)
2243 {
2244         char aaa[64];
2245
2246         if (!cret)
2247                 return -2;
2248         if (session < 0)
2249                 return -2;
2250
2251         sprintf(aaa, "REQT %d", session);
2252         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2253 }
2254
2255
2256 /* SEEN */
2257 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret)
2258 {
2259         char aaa[27];
2260
2261         if (!cret)
2262                 return -2;
2263         if (msgnum < 0)
2264                 return -2;
2265
2266         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2267         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2268 }
2269
2270
2271 /* STLS */
2272 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret)
2273 {
2274         int a;
2275         int r;
2276         char buf[SIZ];
2277
2278 #ifdef HAVE_OPENSSL
2279         SSL *temp_ssl;
2280
2281         /* New SSL object */
2282         temp_ssl = SSL_new(ssl_ctx);
2283         if (!temp_ssl) {
2284                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2285                 return -2;
2286         }
2287         /* Pointless flag waving */
2288 #if SSLEAY_VERSION_NUMBER >= 0x0922
2289         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2290 #endif
2291
2292         /* Associate network connection with SSL object */
2293         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2294                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2295                 return -2;
2296         }
2297
2298         if (status_hook != NULL) {
2299                 status_hook("Requesting encryption...\r");
2300         }
2301
2302         /* Ready to start SSL/TLS */
2303         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2304         if (r / 100 != 2) {
2305                 error_printf("Server can't start TLS: %s\n", buf);
2306                 endtls(temp_ssl);
2307                 return r;
2308         }
2309
2310         /* Do SSL/TLS handshake */
2311         if ((a = SSL_connect(temp_ssl)) < 1) {
2312                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2313                 endtls(temp_ssl);
2314                 return -2;
2315         }
2316         ipc->ssl = temp_ssl;
2317
2318         error_printf("Encrypting with %s cipher %s\n",
2319                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2320             );
2321         return r;
2322 #else
2323         return 0;
2324 #endif                          /* HAVE_OPENSSL */
2325 }
2326
2327
2328 #ifdef HAVE_OPENSSL
2329 static void endtls(SSL * ssl)
2330 {
2331         if (ssl) {
2332                 SSL_shutdown(ssl);
2333                 SSL_free(ssl);
2334         }
2335 }
2336 #endif
2337
2338
2339 /* QDIR */
2340 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret)
2341 {
2342         int ret;
2343         char *aaa;
2344
2345         if (!address)
2346                 return -2;
2347         if (!cret)
2348                 return -2;
2349
2350         aaa = (char *) malloc(strlen(address) + 6);
2351         if (!aaa)
2352                 return -1;
2353
2354         sprintf(aaa, "QDIR %s", address);
2355         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2356         free(aaa);
2357         return ret;
2358 }
2359
2360
2361 /* IPGM */
2362 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret)
2363 {
2364         char aaa[30];
2365
2366         if (!cret)
2367                 return -2;
2368         sprintf(aaa, "IPGM %d", secret);
2369         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2370 }
2371
2372
2373
2374
2375 /* ************************************************************************** */
2376 /*           Stuff below this line is not for public consumption            */
2377 /* ************************************************************************** */
2378
2379
2380 /* Read a listing from the server up to 000.  Append to dest if it exists */
2381 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest)
2382 {
2383         size_t length = 0;
2384         size_t linelength;
2385         char *ret = NULL;
2386         char aaa[SIZ];
2387
2388         ret = dest;
2389         if (ret != NULL) {
2390                 length = strlen(ret);
2391         } else {
2392                 length = 0;
2393         }
2394
2395         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2396                 linelength = strlen(aaa);
2397                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2398                 if (ret) {
2399                         strcpy(&ret[length], aaa);
2400                         length += linelength;
2401                         strcpy(&ret[length++], "\n");
2402                 }
2403         }
2404
2405         return (ret);
2406 }
2407
2408
2409 /* Send a listing to the server; generate the ending 000. */
2410 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing)
2411 {
2412         char *text;
2413
2414         text = (char *) malloc(strlen(listing) + 6);
2415         if (text) {
2416                 strcpy(text, listing);
2417                 while (text[strlen(text) - 1] == '\n')
2418                         text[strlen(text) - 1] = '\0';
2419                 strcat(text, "\n000");
2420                 CtdlIPC_putline(ipc, text);
2421                 free(text);
2422                 text = NULL;
2423         } else {
2424                 /* Malloc failed but we are committed to send */
2425                 /* This may result in extra blanks at the bottom */
2426                 CtdlIPC_putline(ipc, text);
2427                 CtdlIPC_putline(ipc, "000");
2428         }
2429         return 0;
2430 }
2431
2432
2433 /* Partial read of file from server */
2434 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret)
2435 {
2436         size_t len = 0;
2437         char aaa[SIZ];
2438
2439         if (!buf)
2440                 return 0;
2441         if (!cret)
2442                 return 0;
2443         if (bytes < 1)
2444                 return 0;
2445
2446         CtdlIPC_lock(ipc);
2447         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2448         CtdlIPC_putline(ipc, aaa);
2449         CtdlIPC_getline(ipc, aaa);
2450         if (aaa[0] != '6')
2451                 strcpy(cret, &aaa[4]);
2452         else {
2453                 len = extract_long(&aaa[4], 0);
2454                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2455                 if (*buf) {
2456                         /* I know what I'm doing */
2457                         serv_read(ipc, ((char *) (*buf) + offset), len);
2458                 } else {
2459                         /* We have to read regardless */
2460                         serv_read(ipc, aaa, len);
2461                         len = 0;
2462                 }
2463         }
2464         CtdlIPC_unlock(ipc);
2465         return len;
2466 }
2467
2468
2469 /* CLOS */
2470 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret)
2471 {
2472         int ret;
2473
2474         if (!cret)
2475                 return -2;
2476         if (!ipc->downloading)
2477                 return -2;
2478
2479         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2480         if (ret / 100 == 2)
2481                 ipc->downloading = 0;
2482         return ret;
2483 }
2484
2485
2486 /* MSGP */
2487 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats)
2488 {
2489         int ret;
2490         char cmd[SIZ];
2491
2492         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2493         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2494         return ret;
2495 }
2496
2497
2498
2499 /* READ */
2500 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2501                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
2502 {
2503         size_t len;
2504
2505         if (!cret)
2506                 return -1;
2507         if (!buf)
2508                 return -1;
2509         if (*buf)
2510                 return -1;
2511         if (!ipc->downloading)
2512                 return -1;
2513
2514         len = resume;
2515         if (progress_gauge_callback)
2516                 progress_gauge_callback(ipc, len, bytes);
2517         while (len < bytes) {
2518                 size_t block;
2519
2520                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2521                 if (block == 0) {
2522                         free(*buf);
2523                         return 0;
2524                 }
2525                 len += block;
2526                 if (progress_gauge_callback)
2527                         progress_gauge_callback(ipc, len, bytes);
2528         }
2529         return len;
2530 }
2531
2532 /* READ - pipelined */
2533 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2534                                   (CtdlIPC *, unsigned long, unsigned long), char *cret)
2535 {
2536         size_t len;
2537         int calls;              /* How many calls in the pipeline */
2538         int i;                  /* iterator */
2539         char aaa[4096];
2540
2541         if (!cret)
2542                 return -1;
2543         if (!buf)
2544                 return -1;
2545         if (*buf)
2546                 return -1;
2547         if (!ipc->downloading)
2548                 return -1;
2549
2550         *buf = (void *) realloc(*buf, bytes - resume);
2551         if (!*buf)
2552                 return -1;
2553
2554         len = 0;
2555         CtdlIPC_lock(ipc);
2556         if (progress_gauge_callback)
2557                 progress_gauge_callback(ipc, len, bytes);
2558
2559         /* How many calls will be in the pipeline? */
2560         calls = (bytes - resume) / 4096;
2561         if ((bytes - resume) % 4096)
2562                 calls++;
2563
2564         /* Send all requests at once */
2565         for (i = 0; i < calls; i++) {
2566                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2567                 CtdlIPC_putline(ipc, aaa);
2568         }
2569
2570         /* Receive all responses at once */
2571         for (i = 0; i < calls; i++) {
2572                 CtdlIPC_getline(ipc, aaa);
2573                 if (aaa[0] != '6')
2574                         strcpy(cret, &aaa[4]);
2575                 else {
2576                         len = extract_long(&aaa[4], 0);
2577                         /* I know what I'm doing */
2578                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2579                 }
2580                 if (progress_gauge_callback)
2581                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2582         }
2583         CtdlIPC_unlock(ipc);
2584         return len;
2585 }
2586
2587
2588 /* UCLS */
2589 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret)
2590 {
2591         int ret;
2592         char cmd[8];
2593
2594         if (!cret)
2595                 return -1;
2596         if (!ipc->uploading)
2597                 return -1;
2598
2599         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2600         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2601         ipc->uploading = 0;
2602         return ret;
2603 }
2604
2605
2606 /* WRIT */
2607 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2608                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
2609 {
2610         int ret = -1;
2611         size_t offset = 0;
2612         size_t bytes;
2613         char aaa[SIZ];
2614         char buf[4096];
2615         FILE *fd = uploadFP;
2616         int ferr;
2617
2618         if (!cret)
2619                 return -1;
2620
2621         fseek(fd, 0L, SEEK_END);
2622         bytes = ftell(fd);
2623         rewind(fd);
2624
2625         if (progress_gauge_callback)
2626                 progress_gauge_callback(ipc, 0, bytes);
2627
2628         while (offset < bytes) {
2629                 size_t to_write;
2630
2631                 /* Read some data in */
2632                 to_write = fread(buf, 1, 4096, fd);
2633                 if (!to_write) {
2634                         if (feof(fd) || ferror(fd))
2635                                 break;
2636                 }
2637                 sprintf(aaa, "WRIT %d", (int) to_write);
2638                 CtdlIPC_putline(ipc, aaa);
2639                 CtdlIPC_getline(ipc, aaa);
2640                 strcpy(cret, &aaa[4]);
2641                 ret = atoi(aaa);
2642                 if (aaa[0] == '7') {
2643                         to_write = extract_long(&aaa[4], 0);
2644
2645                         serv_write(ipc, buf, to_write);
2646                         offset += to_write;
2647                         if (progress_gauge_callback)
2648                                 progress_gauge_callback(ipc, offset, bytes);
2649                         /* Detect short reads and back up if needed */
2650                         /* offset will never be negative anyway */
2651                         fseek(fd, (signed) offset, SEEK_SET);
2652                 } else {
2653                         break;
2654                 }
2655         }
2656         if (progress_gauge_callback)
2657                 progress_gauge_callback(ipc, 1, 1);
2658         ferr = ferror(fd);
2659         fclose(fd);
2660         return (!ferr ? ret : -2);
2661 }
2662
2663
2664 /*
2665  * Generic command method.  This method should handle any server command
2666  * except for CHAT.  It takes the following arguments:
2667  *
2668  * ipc                  The server to speak with
2669  * command              Preformatted command to send to server
2670  * to_send              A text or binary file to send to server
2671  *                      (only sent if server requests it)
2672  * bytes_to_send        The number of bytes in to_send (required if
2673  *                      sending binary, optional if sending listing)
2674  * to_receive           Pointer to a NULL pointer, if the server
2675  *                      sends text or binary we will allocate memory
2676  *                      for the file and stuff it here
2677  * bytes_to_receive     If a file is received, we will store its
2678  *                      byte count here
2679  * proto_response       The protocol response.  Caller must provide
2680  *                      this buffer and ensure that it is at least
2681  *                      128 bytes in length.
2682  *
2683  * This function returns a number equal to the protocol response number,
2684  * -1 if an internal error occurred, -2 if caller provided bad values,
2685  * or 0 - the protocol response number if bad values were found during
2686  * the protocol exchange.
2687  * It stores the protocol response string (minus the number) in 
2688  * protocol_response as described above.  Some commands send additional
2689  * data in this string.
2690  */
2691 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2692                           const char *command, const char *to_send,
2693                           size_t bytes_to_send, char **to_receive, size_t * bytes_to_receive, char *proto_response)
2694 {
2695         char buf[SIZ];
2696         int ret;
2697
2698         if (!command)
2699                 return -2;
2700         if (!proto_response)
2701                 return -2;
2702
2703         CtdlIPC_lock(ipc);
2704         CtdlIPC_putline(ipc, command);
2705         while (1) {
2706                 CtdlIPC_getline(ipc, proto_response);
2707                 if (proto_response[3] == '*')
2708                         instant_msgs = 1;
2709                 ret = atoi(proto_response);
2710                 strcpy(proto_response, &proto_response[4]);
2711                 switch (ret / 100) {
2712                 default:        /* Unknown, punt */
2713                 case 2: /* OK */
2714                 case 3: /* MORE_DATA */
2715                 case 5: /* ERROR */
2716                         /* Don't need to do anything */
2717                         break;
2718                 case 1: /* LISTING_FOLLOWS */
2719                         if (to_receive && !*to_receive && bytes_to_receive) {
2720                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2721                         } else {        /* Drain */
2722                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2723                                 ret = -ret;
2724                         }
2725                         break;
2726                 case 4: /* SEND_LISTING */
2727                         if (to_send) {
2728                                 CtdlIPCSendListing(ipc, to_send);
2729                         } else {
2730                                 /* No listing given, fake it */
2731                                 CtdlIPC_putline(ipc, "000");
2732                                 ret = -ret;
2733                         }
2734                         break;
2735                 case 6: /* BINARY_FOLLOWS */
2736                         if (to_receive && !*to_receive && bytes_to_receive) {
2737                                 *bytes_to_receive = extract_long(proto_response, 0);
2738                                 *to_receive = (char *)
2739                                     malloc((size_t) * bytes_to_receive);
2740                                 if (!*to_receive) {
2741                                         ret = -1;
2742                                 } else {
2743                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2744                                 }
2745                         } else {
2746                                 /* Drain */
2747                                 size_t drain;
2748
2749                                 drain = extract_long(proto_response, 0);
2750                                 while (drain > SIZ) {
2751                                         serv_read(ipc, buf, SIZ);
2752                                         drain -= SIZ;
2753                                 }
2754                                 serv_read(ipc, buf, drain);
2755                                 ret = -ret;
2756                         }
2757                         break;
2758                 case 7: /* SEND_BINARY */
2759                         if (to_send && bytes_to_send) {
2760                                 serv_write(ipc, to_send, bytes_to_send);
2761                         } else if (bytes_to_send) {
2762                                 /* Fake it, send nulls */
2763                                 size_t fake;
2764
2765                                 fake = bytes_to_send;
2766                                 memset(buf, '\0', SIZ);
2767                                 while (fake > SIZ) {
2768                                         serv_write(ipc, buf, SIZ);
2769                                         fake -= SIZ;
2770                                 }
2771                                 serv_write(ipc, buf, fake);
2772                                 ret = -ret;
2773                         }       /* else who knows?  DANGER WILL ROBINSON */
2774                         break;
2775                 case 8: /* START_CHAT_MODE */
2776                         if (!strncasecmp(command, "CHAT", 4)) {
2777                                 /* Don't call chatmode with generic! */
2778                                 CtdlIPC_putline(ipc, "/quit");
2779                                 ret = -ret;
2780                         } else {
2781                                 /* In this mode we send then receive listing */
2782                                 if (to_send) {
2783                                         CtdlIPCSendListing(ipc, to_send);
2784                                 } else {
2785                                         /* No listing given, fake it */
2786                                         CtdlIPC_putline(ipc, "000");
2787                                         ret = -ret;
2788                                 }
2789                                 if (to_receive && !*to_receive && bytes_to_receive) {
2790                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2791                                 } else {        /* Drain */
2792                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2793                                         ret = -ret;
2794                                 }
2795                         }
2796                         break;
2797                 case 9: /* ASYNC_MSG */
2798                         /* CtdlIPCDoAsync(ret, proto_response); */
2799                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2800                         break;
2801                 }
2802                 if (ret / 100 != 9)
2803                         break;
2804         }
2805         CtdlIPC_unlock(ipc);
2806         return ret;
2807 }
2808
2809
2810 /*
2811  * Connect to a Citadel on a remote host using a TCP/IP socket
2812  */
2813 static int tcp_connectsock(char *host, char *service)
2814 {
2815         struct in6_addr serveraddr;
2816         struct addrinfo hints;
2817         struct addrinfo *res = NULL;
2818         struct addrinfo *ai = NULL;
2819         int rc = (-1);
2820         int sock = (-1);
2821
2822         if ((host == NULL) || IsEmptyStr(host)) {
2823                 service = DEFAULT_HOST;
2824         }
2825         if ((service == NULL) || IsEmptyStr(service)) {
2826                 service = DEFAULT_PORT;
2827         }
2828
2829         memset(&hints, 0x00, sizeof(hints));
2830         hints.ai_flags = AI_NUMERICSERV;
2831         hints.ai_family = AF_UNSPEC;
2832         hints.ai_socktype = SOCK_STREAM;
2833
2834         /*
2835          * Handle numeric IPv4 and IPv6 addresses
2836          */
2837         rc = inet_pton(AF_INET, host, &serveraddr);
2838         if (rc == 1) {          /* dotted quad */
2839                 hints.ai_family = AF_INET;
2840                 hints.ai_flags |= AI_NUMERICHOST;
2841         } else {
2842                 rc = inet_pton(AF_INET6, host, &serveraddr);
2843                 if (rc == 1) {  /* IPv6 address */
2844                         hints.ai_family = AF_INET6;
2845                         hints.ai_flags |= AI_NUMERICHOST;
2846                 }
2847         }
2848
2849         /* Begin the connection process */
2850
2851         rc = getaddrinfo(host, service, &hints, &res);
2852         if (rc != 0) {
2853                 return (-1);
2854         }
2855
2856         /*
2857          * Try all available addresses until we connect to one or until we run out.
2858          */
2859         for (ai = res; ai != NULL; ai = ai->ai_next) {
2860                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2861                 if (sock < 0)
2862                         return (-1);
2863
2864                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2865                 if (rc >= 0) {
2866                         return (sock);  /* Connected! */
2867                 } else {
2868                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2869                 }
2870         }
2871
2872         return (-1);
2873 }
2874
2875
2876
2877
2878
2879 /*
2880  * Connect to a Citadel on the local host using a unix domain socket
2881  */
2882 static int uds_connectsock(int *isLocal, char *sockpath)
2883 {
2884         struct sockaddr_un addr;
2885         int s;
2886
2887         memset(&addr, 0, sizeof(addr));
2888         addr.sun_family = AF_UNIX;
2889         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2890
2891         s = socket(AF_UNIX, SOCK_STREAM, 0);
2892         if (s < 0) {
2893                 return -1;
2894         }
2895
2896         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2897                 close(s);
2898                 return -1;
2899         }
2900
2901         *isLocal = 1;
2902         return s;
2903 }
2904
2905
2906 /*
2907  * input binary data from socket
2908  */
2909 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes)
2910 {
2911         unsigned int len, rlen;
2912
2913 #if defined(HAVE_OPENSSL)
2914         if (ipc->ssl) {
2915                 serv_read_ssl(ipc, buf, bytes);
2916                 return;
2917         }
2918 #endif
2919         len = 0;
2920         while (len < bytes) {
2921                 rlen = read(ipc->sock, &buf[len], bytes - len);
2922                 if (rlen < 1) {
2923                         connection_died(ipc, 0);
2924                         return;
2925                 }
2926                 len += rlen;
2927         }
2928 }
2929
2930
2931 /*
2932  * send binary to server
2933  */
2934 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
2935 {
2936         unsigned int bytes_written = 0;
2937         int retval;
2938
2939 #if defined(HAVE_OPENSSL)
2940         if (ipc->ssl) {
2941                 serv_write_ssl(ipc, buf, nbytes);
2942                 return;
2943         }
2944 #endif
2945         while (bytes_written < nbytes) {
2946                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2947                 if (retval < 1) {
2948                         connection_died(ipc, 0);
2949                         return;
2950                 }
2951                 bytes_written += retval;
2952         }
2953 }
2954
2955
2956 #ifdef HAVE_OPENSSL
2957 /*
2958  * input binary data from encrypted connection
2959  */
2960 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes)
2961 {
2962         int len, rlen;
2963         char junk[1];
2964
2965         len = 0;
2966         while (len < bytes) {
2967                 if (SSL_want_read(ipc->ssl)) {
2968                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2969                                 error_printf("SSL_write in serv_read:\n");
2970                                 ERR_print_errors_fp(stderr);
2971                         }
2972                 }
2973                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2974                 if (rlen < 1) {
2975                         long errval;
2976
2977                         errval = SSL_get_error(ipc->ssl, rlen);
2978                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2979                                 sleep(1);
2980                                 continue;
2981                         }
2982 /***
2983  Not sure why we'd want to handle these error codes any differently,
2984  but this definitely isn't the way to handle them.  Someone must have
2985  naively assumed that we could fall back to unencrypted communications,
2986  but all it does is just recursively blow the stack.
2987                         if (errval == SSL_ERROR_ZERO_RETURN ||
2988                                         errval == SSL_ERROR_SSL) {
2989                                 serv_read(ipc, &buf[len], bytes - len);
2990                                 return;
2991                         }
2992  ***/
2993                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
2994                         connection_died(ipc, 1);
2995                         return;
2996                 }
2997                 len += rlen;
2998         }
2999 }
3000
3001
3002 /*
3003  * send binary to server encrypted
3004  */
3005 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
3006 {
3007         unsigned int bytes_written = 0;
3008         int retval;
3009         char junk[1];
3010
3011         while (bytes_written < nbytes) {
3012                 if (SSL_want_write(ipc->ssl)) {
3013                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
3014                                 error_printf("SSL_read in serv_write:\n");
3015                                 ERR_print_errors_fp(stderr);
3016                         }
3017                 }
3018                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
3019                 if (retval < 1) {
3020                         long errval;
3021
3022                         errval = SSL_get_error(ipc->ssl, retval);
3023                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3024                                 sleep(1);
3025                                 continue;
3026                         }
3027                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
3028                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
3029                                 return;
3030                         }
3031                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
3032                         connection_died(ipc, 1);
3033                         return;
3034                 }
3035                 bytes_written += retval;
3036         }
3037 }
3038
3039
3040
3041
3042 static void CtdlIPC_init_OpenSSL(void)
3043 {
3044         int a;
3045         const SSL_METHOD *ssl_method;
3046         DH *dh;
3047
3048         /* already done init */
3049         if (ssl_ctx) {
3050                 return;
3051         }
3052
3053         /* Get started */
3054         a = 0;
3055         ssl_ctx = NULL;
3056         dh = NULL;
3057         SSL_load_error_strings();
3058         SSLeay_add_ssl_algorithms();
3059
3060         /* Set up the SSL context in which we will oeprate */
3061         ssl_method = SSLv23_client_method();
3062         ssl_ctx = SSL_CTX_new(ssl_method);
3063         if (!ssl_ctx) {
3064                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
3065                 return;
3066         }
3067         /* Any reasonable cipher we can get */
3068         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3069                 error_printf("No ciphers available for encryption\n");
3070                 return;
3071         }
3072         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3073
3074         /* Load DH parameters into the context */
3075         dh = DH_new();
3076         if (!dh) {
3077                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
3078                 return;
3079         }
3080
3081         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
3082                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
3083                 DH_free(dh);
3084                 return;
3085         }
3086
3087         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3088         DH_free(dh);
3089 }
3090
3091 #endif                          /* HAVE_OPENSSL */
3092
3093
3094 int ReadNetworkChunk(CtdlIPC * ipc)
3095 {
3096         fd_set read_fd;
3097         int ret = 0;
3098         int err = 0;
3099         struct timeval tv;
3100         size_t n;
3101
3102         tv.tv_sec = 1;
3103         tv.tv_usec = 1000;
3104         /*tries = 0; */
3105         n = 0;
3106         while (1) {
3107                 errno = 0;
3108                 FD_ZERO(&read_fd);
3109                 FD_SET(ipc->sock, &read_fd);
3110                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
3111
3112                 if (ret > 0) {
3113
3114                         *(ipc->BufPtr) = '\0';
3115                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3116                         if (n > 0) {
3117                                 ipc->BufPtr[n] = '\0';
3118                                 ipc->BufUsed += n;
3119                                 return n;
3120                         } else
3121                                 return n;
3122                 } else if (ret < 0) {
3123                         if (!(errno == EINTR || errno == EAGAIN))
3124                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3125                         return -1;
3126                 }               /*
3127                                    else {
3128                                    tries ++;
3129                                    if (tries >= 10)
3130                                    n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
3131                                    if (n > 0) {
3132                                    ipc->BufPtr[n]='\0';
3133                                    ipc->BufUsed += n;
3134                                    return n;
3135                                    }
3136                                    else {
3137                                    connection_died(ipc, 0);
3138                                    return -1;
3139                                    }
3140                                    } */
3141         }
3142 }
3143
3144 /*
3145  * input string from socket - implemented in terms of serv_read()
3146  */
3147 #ifdef CHUNKED_READ
3148
3149 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3150 {
3151         int i, ntries;
3152         char *aptr, *bptr, *aeptr, *beptr;
3153
3154 //      error_printf("---\n");
3155
3156         beptr = buf + SIZ;
3157 #if defined(HAVE_OPENSSL)
3158         if (ipc->ssl) {
3159
3160                 /* Read one character at a time. */
3161                 for (i = 0;; i++) {
3162                         serv_read(ipc, &buf[i], 1);
3163                         if (buf[i] == '\n' || i == (SIZ - 1))
3164                                 break;
3165                 }
3166
3167                 /* If we got a long line, discard characters until the newline. */
3168                 if (i == (SIZ - 1))
3169                         while (buf[i] != '\n')
3170                                 serv_read(ipc, &buf[i], 1);
3171
3172                 /* Strip the trailing newline (and carriage return, if present) */
3173                 if (i >= 0 && buf[i] == 10)
3174                         buf[i--] = 0;
3175                 if (i >= 0 && buf[i] == 13)
3176                         buf[i--] = 0;
3177         } else
3178 #endif
3179         {
3180                 if (ipc->Buf == NULL) {
3181                         ipc->BufSize = SIZ;
3182                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3183                         *(ipc->Buf) = '\0';
3184                         ipc->BufPtr = ipc->Buf;
3185                 }
3186
3187                 ntries = 0;
3188 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3189                 if (ipc->BufUsed == 0)
3190                         ReadNetworkChunk(ipc);
3191
3192 ////            if (ipc->BufUsed != 0) while (1)
3193                 bptr = buf;
3194
3195                 while (1) {
3196                         aptr = ipc->BufPtr;
3197                         aeptr = ipc->Buf + ipc->BufSize;
3198                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3199                                 *(bptr++) = *(aptr++);
3200                         if ((*aptr == '\n') && (aptr < aeptr)) {
3201                                 /* Terminate it right, remove the line breaks */
3202                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3203                                         aptr++;
3204                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3205                                         aptr++;
3206                                 *(bptr++) = '\0';
3207 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3208                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3209                                         *(--bptr) = '\0';
3210
3211                                 /* is there more in the buffer we need to read later? */
3212                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3213                                         ipc->BufPtr = aptr;
3214                                 } else {
3215                                         ipc->BufUsed = 0;
3216                                         ipc->BufPtr = ipc->Buf;
3217                                 }
3218 //                              error_printf("----bla6\n");
3219                                 return;
3220
3221                         } /* should we move our read stuf to the bufferstart so we have more space at the end? */
3222                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3223                                 size_t NewBufSize = ipc->BufSize * 2;
3224                                 int delta = (ipc->BufPtr - ipc->Buf);
3225                                 char *NewBuf;
3226
3227                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3228                                 NewBuf = (char *) malloc(NewBufSize + 10);
3229                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3230                                 free(ipc->Buf);
3231                                 ipc->Buf = ipc->BufPtr = NewBuf;
3232                                 ipc->BufUsed -= delta;
3233                                 ipc->BufSize = NewBufSize;
3234                         }
3235                         if (ReadNetworkChunk(ipc) < 0) {
3236 //                              error_printf("----bla\n");
3237                                 return;
3238                         }
3239                 }
3240 ///             error_printf("----bl45761%s\nipc->BufUsed");
3241         }
3242 //      error_printf("----bla1\n");
3243 }
3244
3245 #else                           /* CHUNKED_READ */
3246
3247 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3248 {
3249         int i;
3250
3251         /* Read one character at a time. */
3252         for (i = 0;; i++) {
3253                 serv_read(ipc, &buf[i], 1);
3254                 if (buf[i] == '\n' || i == (SIZ - 1))
3255                         break;
3256         }
3257
3258         /* If we got a long line, discard characters until the newline. */
3259         if (i == (SIZ - 1))
3260                 while (buf[i] != '\n')
3261                         serv_read(ipc, &buf[i], 1);
3262
3263         /* Strip the trailing newline (and carriage return, if present) */
3264         if (i >= 0 && buf[i] == 10)
3265                 buf[i--] = 0;
3266         if (i >= 0 && buf[i] == 13)
3267                 buf[i--] = 0;
3268 }
3269
3270
3271 #endif                          /* CHUNKED_READ */
3272
3273
3274 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf)
3275 {
3276         CtdlIPC_getline(ipc, buf);
3277 }
3278
3279 /*
3280  * send line to server - implemented in terms of serv_write()
3281  */
3282 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf)
3283 {
3284         char *cmd = NULL;
3285         int len;
3286
3287         len = strlen(buf);
3288         cmd = malloc(len + 2);
3289         if (!cmd) {
3290                 /* This requires no extra memory */
3291                 serv_write(ipc, buf, len);
3292                 serv_write(ipc, "\n", 1);
3293         } else {
3294                 /* This is network-optimized */
3295                 strncpy(cmd, buf, len);
3296                 strcpy(cmd + len, "\n");
3297                 serv_write(ipc, cmd, len + 1);
3298                 free(cmd);
3299         }
3300
3301         ipc->last_command_sent = time(NULL);
3302 }
3303
3304 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf)
3305 {
3306         CtdlIPC_putline(ipc, buf);
3307 }
3308
3309
3310 /*
3311  * attach to server
3312  */
3313 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3314 {
3315         int a;
3316         char cithost[SIZ];
3317         char citport[SIZ];
3318         char sockpath[SIZ];
3319         CtdlIPC *ipc;
3320
3321         ipc = malloc(sizeof(struct _CtdlIPC));
3322         if (!ipc) {
3323                 return 0;
3324         }
3325 #if defined(HAVE_OPENSSL)
3326         ipc->ssl = NULL;
3327         CtdlIPC_init_OpenSSL();
3328 #endif
3329         ipc->sock = -1;         /* Not connected */
3330         ipc->isLocal = 0;       /* Not local, of course! */
3331         ipc->downloading = 0;
3332         ipc->uploading = 0;
3333         ipc->last_command_sent = 0L;
3334         ipc->network_status_cb = NULL;
3335         ipc->Buf = NULL;
3336         ipc->BufUsed = 0;
3337         ipc->BufPtr = NULL;
3338
3339         strcpy(cithost, DEFAULT_HOST);  /* default host */
3340         strcpy(citport, DEFAULT_PORT);  /* default port */
3341
3342         /* Allow caller to supply our values */
3343         if (hostbuf && strlen(hostbuf) > 0) {
3344                 strcpy(cithost, hostbuf);
3345         }
3346         if (portbuf && strlen(portbuf) > 0) {
3347                 strcpy(citport, portbuf);
3348         }
3349
3350         /* Read host/port from command line if present */
3351         for (a = 0; a < argc; ++a) {
3352                 if (a == 0) {
3353                         /* do nothing */
3354                 } else if (a == 1) {
3355                         strcpy(cithost, argv[a]);
3356                 } else if (a == 2) {
3357                         strcpy(citport, argv[a]);
3358                 } else {
3359                         error_printf("%s: usage: ", argv[0]);
3360                         error_printf("%s [host] [port] ", argv[0]);
3361                         free(ipc);
3362                         errno = EINVAL;
3363                         return 0;
3364                 }
3365         }
3366
3367         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3368                 ipc->isLocal = 1;
3369         }
3370
3371         /* If we're using a unix domain socket we can do a bunch of stuff */
3372         if (!strcmp(cithost, UDS)) {
3373                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3374                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3375                 } else {
3376                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3377                 }
3378                 printf("[%s]\n", sockpath);
3379                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3380                 if (ipc->sock == -1) {
3381                         free(ipc);
3382                         return 0;
3383                 }
3384                 if (hostbuf != NULL)
3385                         strcpy(hostbuf, cithost);
3386                 if (portbuf != NULL)
3387                         strcpy(portbuf, sockpath);
3388                 strcpy(ipc->ip_hostname, "");
3389                 strcpy(ipc->ip_address, "");
3390                 return ipc;
3391         }
3392
3393         printf("[%s:%s]\n", cithost, citport);
3394         ipc->sock = tcp_connectsock(cithost, citport);
3395         if (ipc->sock == -1) {
3396                 free(ipc);
3397                 return 0;
3398         }
3399
3400
3401         /* Learn the actual network identity of the host to which we are connected */
3402
3403         struct sockaddr_in6 clientaddr;
3404         unsigned int addrlen = sizeof(clientaddr);
3405
3406         ipc->ip_hostname[0] = 0;
3407         ipc->ip_address[0] = 0;
3408
3409         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3410         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3411         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3412
3413         /* stuff other things elsewhere */
3414
3415         if (hostbuf != NULL)
3416                 strcpy(hostbuf, cithost);
3417         if (portbuf != NULL)
3418                 strcpy(portbuf, citport);
3419         return ipc;
3420 }
3421
3422
3423 /*
3424  * Disconnect and delete the IPC class (destructor)
3425  */
3426 void CtdlIPC_delete(CtdlIPC * ipc)
3427 {
3428 #ifdef HAVE_OPENSSL
3429         if (ipc->ssl) {
3430                 SSL_shutdown(ipc->ssl);
3431                 SSL_free(ipc->ssl);
3432                 ipc->ssl = NULL;
3433         }
3434 #endif
3435         if (ipc->sock > -1) {
3436                 shutdown(ipc->sock, 2); /* Close it up */
3437                 ipc->sock = -1;
3438         }
3439         if (ipc->Buf != NULL)
3440                 free(ipc->Buf);
3441         ipc->Buf = NULL;
3442         ipc->BufPtr = NULL;
3443         free(ipc);
3444 }
3445
3446
3447 /*
3448  * Disconnect and delete the IPC class (destructor)
3449  * Also NULLs out the pointer
3450  */
3451 void CtdlIPC_delete_ptr(CtdlIPC ** pipc)
3452 {
3453         CtdlIPC_delete(*pipc);
3454         *pipc = NULL;
3455 }
3456
3457
3458 /*
3459  * return the file descriptor of the server socket so we can select() on it.
3460  *
3461  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3462  * rewritten...
3463  */
3464 int CtdlIPC_getsockfd(CtdlIPC * ipc)
3465 {
3466         return ipc->sock;
3467 }
3468
3469
3470 /*
3471  * return one character
3472  *
3473  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3474  * rewritten...
3475  */
3476 char CtdlIPC_get(CtdlIPC * ipc)
3477 {
3478         char buf[2];
3479         char ch;
3480
3481         serv_read(ipc, buf, 1);
3482         ch = (int) buf[0];
3483
3484         return (ch);
3485 }