I will not bow to the demands of social justice warriors. Die, fuckers.
[citadel.git] / citadel / modules / image / serv_image.c
1 /*
2  * Copyright (c) 1987-2018 by the citadel.org team
3  *
4  * This program is open source software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include "ctdl_module.h"
16 #include "config.h"
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <dirent.h>
20
21
22 /*
23  * DownLoad Room Image (see its icon or whatever)
24  * If this command succeeds, it follows the same protocol as the DLAT command.
25  */
26 void cmd_dlri(char *cmdbuf)
27 {
28         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
29         if (CC->room.msgnum_pic < 1) {
30                 cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
31                 return;
32         }
33
34         struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_pic, 1, 1);
35         if (msg != NULL) {
36                 // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLRI command
37                 // to have the same output format as the DLAT command, because it calls the same code.
38                 // For example: 600 402132|-1||image/gif|
39                 safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
40                 CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
41                 CM_Free(msg);
42         }
43         else {
44                 cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
45                 return;
46         }
47 }
48
49
50 /*
51  * UpLoad Room Image (avatar or photo or whatever)
52  */
53 void cmd_ulri(char *cmdbuf)
54 {
55         long data_length;
56         char mimetype[SIZ];
57
58         if (CtdlAccessCheck(ac_room_aide)) return;
59
60         data_length = extract_long(cmdbuf, 0);
61         extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
62
63         if (data_length < 20) {
64                 cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
65                 return;
66         }
67
68         if (strncasecmp(mimetype, "image/", 6)) {
69                 cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
70                 return;
71         }
72
73         char *unencoded_data = malloc(data_length + 1);
74         if (!unencoded_data) {
75                 cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
76                 return;
77         }
78
79         cprintf("%d %ld\n", SEND_BINARY, data_length);
80         client_read(unencoded_data, data_length);
81
82         // We've got the data read from the client, now save it.
83         char *encoded_data = malloc((data_length * 2) + 100);
84         if (encoded_data) {
85                 sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
86                 CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
87                 long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, encoded_data, FMT_RFC822, "Image uploaded by admin user");
88
89                 if (CtdlGetRoomLock(&CC->room, CC->room.QRname) == 0) {
90                         long old_msgnum = CC->room.msgnum_pic;
91                         syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, CC->room.QRname);
92                         CC->room.msgnum_pic = new_msgnum;
93                         CtdlPutRoomLock(&CC->room);
94                         if (old_msgnum > 0) {
95                                 syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, SYSCONFIGROOM);
96                                 CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
97                         }
98                 }
99                 free(encoded_data);
100         }
101
102         free(unencoded_data);
103 }
104
105
106 /*
107  * DownLoad User Image (see their avatar or photo or whatever)
108  * If this command succeeds, it follows the same protocol as the DLAT command.
109  */
110 void cmd_dlui(char *cmdbuf)
111 {
112         struct ctdluser ruser;
113         char buf[SIZ];
114
115         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
116         extract_token(buf, cmdbuf, 0, '|', sizeof buf);
117         if (CtdlGetUser(&ruser, buf) != 0) {
118                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
119                 return;
120         }
121         if (ruser.msgnum_pic < 1) {
122                 cprintf("%d No image found.\n", ERROR + FILE_NOT_FOUND);
123                 return;
124         }
125
126         struct CtdlMessage *msg = CtdlFetchMessage(ruser.msgnum_pic, 1, 1);
127         if (msg != NULL) {
128                 // The call to CtdlOutputPreLoadedMsg() with MT_SPEW_SECTION will cause the DLUI command
129                 // to have the same output format as the DLAT command, because it calls the same code.
130                 // For example: 600 402132|-1||image/gif|
131                 safestrncpy(CC->download_desired_section, "1", sizeof CC->download_desired_section);
132                 CtdlOutputPreLoadedMsg(msg, MT_SPEW_SECTION, HEADERS_NONE, 1, 0, 0);
133                 CM_Free(msg);
134         }
135         else {
136                 cprintf("%d No image found.\n", ERROR + MESSAGE_NOT_FOUND);
137                 return;
138         }
139 }
140
141
142 /*
143  * UpLoad User Image (avatar or photo or whatever)
144  */
145 void cmd_ului(char *cmdbuf)
146 {
147         long data_length;
148         char mimetype[SIZ];
149         char username[USERNAME_SIZE];
150         char userconfigroomname[ROOMNAMELEN];
151
152         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
153
154         if (num_parms(cmdbuf) < 2)
155         {
156                 cprintf("%d Usage error\n", ERROR + ILLEGAL_VALUE);
157                 return;
158         }
159
160         data_length = extract_long(cmdbuf, 0);
161         extract_token(mimetype, cmdbuf, 1, '|', sizeof mimetype);
162         extract_token(username, cmdbuf, 2, '|', sizeof username);
163
164         if (data_length < 20) {
165                 cprintf("%d That's an awfully small file.  Try again.\n", ERROR + ILLEGAL_VALUE);
166                 return;
167         }
168
169         if (strncasecmp(mimetype, "image/", 6)) {
170                 cprintf("%d Only image files are permitted.\n", ERROR + ILLEGAL_VALUE);
171                 return;
172         }
173
174         if (IsEmptyStr(username)) {
175                 safestrncpy(username, CC->curr_user, sizeof username);
176         }
177
178         // Normal users can only change their own photo
179         if ( (strcasecmp(username, CC->curr_user)) && (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) ) {
180                 cprintf("%d Higher access required to change another user's photo.\n", ERROR + HIGHER_ACCESS_REQUIRED);
181         }
182
183         // Check to make sure the user exists
184         struct ctdluser usbuf;
185         if (CtdlGetUser(&usbuf, username) != 0) {               // check for existing user, don't lock it yet
186                 cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER , username);
187                 return;
188         }
189         CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
190
191         char *unencoded_data = malloc(data_length + 1);
192         if (!unencoded_data) {
193                 cprintf("%d Could not allocate %ld bytes of memory\n", ERROR + INTERNAL_ERROR , data_length);
194                 return;
195         }
196
197         cprintf("%d %ld\n", SEND_BINARY, data_length);
198         client_read(unencoded_data, data_length);
199
200         // We've got the data read from the client, now save it.
201         char *encoded_data = malloc((data_length * 2) + 100);
202         if (encoded_data) {
203                 sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", mimetype);
204                 CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
205                 long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo uploaded by user");
206
207                 if (CtdlGetUserLock(&usbuf, username) == 0) {   // lock it this time
208                         long old_msgnum = usbuf.msgnum_pic;
209                         syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
210                         usbuf.msgnum_pic = new_msgnum;
211                         CtdlPutUserLock(&usbuf);
212                         if (old_msgnum > 0) {
213                                 syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
214                                 CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
215                         }
216                 }
217
218                 free(encoded_data);
219         }
220
221         free(unencoded_data);
222 }
223
224
225 /*
226  * Import function called by import_old_userpic_files() for a single user
227  */
228 void import_one_userpic_file(char *username, long usernum, char *path)
229 {
230         syslog(LOG_DEBUG, "Import legacy userpic for %s, usernum=%ld, filename=%s", username, usernum, path);
231
232         FILE *fp = fopen(path, "r");
233         if (!fp) return;
234
235         fseek(fp, 0, SEEK_END);
236         long data_length = ftell(fp);
237
238         if (data_length >= 1) {
239                 rewind(fp);
240                 char *unencoded_data = malloc(data_length);
241                 if (unencoded_data) {
242                         fread(unencoded_data, data_length, 1, fp);
243                         char *encoded_data = malloc((data_length * 2) + 100);
244                         if (encoded_data) {
245                                 sprintf(encoded_data, "Content-type: %s\nContent-transfer-encoding: base64\n\n", GuessMimeByFilename(path, strlen(path)));
246                                 CtdlEncodeBase64(&encoded_data[strlen(encoded_data)], unencoded_data, data_length, 1);
247
248                                 char userconfigroomname[ROOMNAMELEN];
249                                 struct ctdluser usbuf;
250
251                                 if (CtdlGetUser(&usbuf, username) == 0) {       // no need to lock it , we are still initializing
252                                         long old_msgnum = usbuf.msgnum_pic;
253                                         CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &usbuf, USERCONFIGROOM);
254                                         long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, encoded_data, FMT_RFC822, "Photo imported from file");
255                                         syslog(LOG_DEBUG, "Message %ld is now the photo for %s", new_msgnum, username);
256                                         usbuf.msgnum_pic = new_msgnum;
257                                         CtdlPutUser(&usbuf);
258                                         unlink(path);                           // delete the old file , it's in the database now
259                                         if (old_msgnum > 0) {
260                                                 syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
261                                                 CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
262                                         }
263                                 }
264                                 free(encoded_data);
265                         }
266                         free(unencoded_data);
267                 }
268         }
269         fclose(fp);
270 }
271
272
273 /*
274  * Look for old-format "userpic" files and import them into the message base
275  */
276 void import_old_userpic_files(void)
277 {
278         DIR *filedir = NULL;
279         struct dirent *filedir_entry;
280         size_t d_namelen;
281         struct ctdluser usbuf;
282         long usernum = 0;
283         int d_type = 0;
284         struct stat s;
285         char path[PATH_MAX];
286
287
288         syslog(LOG_DEBUG, "Importing old style userpic files into the message base");
289         filedir = opendir (ctdl_usrpic_dir);
290         if (filedir == NULL) {
291                 return;
292         }
293         while ( (filedir_entry = readdir(filedir)) , (filedir_entry != NULL))
294         {
295 #ifdef _DIRENT_HAVE_D_NAMLEN
296                 d_namelen = filedir_entry->d_namlen;
297
298 #else
299                 d_namelen = strlen(filedir_entry->d_name);
300 #endif
301
302 #ifdef _DIRENT_HAVE_D_TYPE
303                 d_type = filedir_entry->d_type;
304 #else
305
306 #ifndef DT_UNKNOWN
307 #define DT_UNKNOWN     0
308 #define DT_DIR         4
309 #define DT_REG         8
310 #define DT_LNK         10
311
312 #define IFTODT(mode)   (((mode) & 0170000) >> 12)
313 #define DTTOIF(dirtype)        ((dirtype) << 12)
314 #endif
315                 d_type = DT_UNKNOWN;
316 #endif
317                 if ((d_namelen == 1) && 
318                     (filedir_entry->d_name[0] == '.'))
319                         continue;
320
321                 if ((d_namelen == 2) && 
322                     (filedir_entry->d_name[0] == '.') &&
323                     (filedir_entry->d_name[1] == '.'))
324                         continue;
325
326                 snprintf(path, PATH_MAX, "%s/%s", ctdl_usrpic_dir, filedir_entry->d_name);
327                 if (d_type == DT_UNKNOWN) {
328                         if (lstat(path, &s) == 0) {
329                                 d_type = IFTODT(s.st_mode);
330                         }
331                 }
332                 switch (d_type)
333                 {
334                 case DT_DIR:
335                         break;
336                 case DT_LNK:
337                 case DT_REG:
338                         usernum = atol(filedir_entry->d_name);
339                         if (CtdlGetUserByNumber(&usbuf, usernum) == 0) {
340                                 import_one_userpic_file(usbuf.fullname, usernum, path);
341                         }
342                 }
343         }
344         closedir(filedir);
345         rmdir(ctdl_usrpic_dir);
346 }
347
348
349
350 CTDL_MODULE_INIT(image)
351 {
352         if (!threading)
353         {
354                 import_old_userpic_files();
355                 CtdlRegisterProtoHook(cmd_dlri, "DLRI", "DownLoad Room Image");
356                 CtdlRegisterProtoHook(cmd_ulri, "ULRI", "UpLoad Room Image");
357                 CtdlRegisterProtoHook(cmd_dlui, "DLUI", "DownLoad User Image");
358                 CtdlRegisterProtoHook(cmd_ului, "ULUI", "UpLoad User Image");
359         }
360         /* return our module name for the log */
361         return "image";
362 }